FFMPEG 将音频文件转换为MP4容器中的AAC文件 ,翻译官方示例


/*
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
/**
 * @file
 * simple audio converter
 *
 * @example transcode_aac.c
 * Convert an input audio file to AAC in an MP4 container using FFmpeg.
 * @author Andreas Unterweger (dustsigns@gmail.com)
 */
#include <stdio.h>
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/frame.h"
#include "libavutil/opt.h"
#include "libswresample/swresample.h"
/** 输出比特率 kbit/s */
#define OUTPUT_BIT_RATE 96000
/** 输出声道数 */
#define OUTPUT_CHANNELS 2
/** 打开输入文件和请求解码器 */
static int open_input_file(const char *filename,
                           AVFormatContext **input_format_context,
                           AVCodecContext **input_codec_context)
{
    AVCodecContext *avctx;
    AVCodec *input_codec;
    int error;
    /** 读取输入文件 */
    if ((error = avformat_open_input(input_format_context, filename, NULL,
                                     NULL)) < 0) {
        fprintf(stderr, "Could not open input file '%s' (error '%s')\n",
                filename, av_err2str(error));
        *input_format_context = NULL;
        return error;
    }
    /** 从输入文件读取信息,如数据流数等 */
    if ((error = avformat_find_stream_info(*input_format_context, NULL)) < 0) {
        fprintf(stderr, "Could not open find stream info (error '%s')\n",
                av_err2str(error));
        avformat_close_input(input_format_context);
        return error;
    }
    /** 确保只有一个输入流 */
    if ((*input_format_context)->nb_streams != 1) {
        fprintf(stderr, "Expected one audio input stream, but found %d\n",
                (*input_format_context)->nb_streams);
        avformat_close_input(input_format_context);
        return AVERROR_EXIT;
    }
    /** 根据音频数据流中编码方式查找对应解码器 */
    if (!(input_codec = avcodec_find_decoder((*input_format_context)->streams[0]->codecpar->codec_id))) {
        fprintf(stderr, "Could not find input codec\n");
        avformat_close_input(input_format_context);
        return AVERROR_EXIT;
    }
    /** 创建一个解码器上下文 */
    avctx = avcodec_alloc_context3(input_codec);
    if (!avctx) {
        fprintf(stderr, "Could not allocate a decoding context\n");
        avformat_close_input(input_format_context);
        return AVERROR(ENOMEM);
    }
    /** 用混合器参数初始化解码器上下文参数 */
    error = avcodec_parameters_to_context(avctx, (*input_format_context)->streams[0]->codecpar);
    if (error < 0) {
        avformat_close_input(input_format_context);
        avcodec_free_context(&avctx);
        return error;
    }
    /** 为音频流打开一个编码器稍后使用 */
    if ((error = avcodec_open2(avctx, input_codec, NULL)) < 0) {
        fprintf(stderr, "Could not open input codec (error '%s')\n",
                av_err2str(error));
        avcodec_free_context(&avctx);
        avformat_close_input(input_format_context);
        return error;
    }
    /** 保存解码器指针 */
    *input_codec_context = avctx;
    return 0;
}


/**
 * 打开一个输出文件并请求一个编码器
 * 同时设置一些基础参数
 * 有一些参数是介于输入文件的参数
 */
static int open_output_file(const char *filename,
                            AVCodecContext *input_codec_context,
                            AVFormatContext **output_format_context,
                            AVCodecContext **output_codec_context)
{
    AVCodecContext *avctx          = NULL;
    AVIOContext *output_io_context = NULL;
    AVStream *stream               = NULL;
    AVCodec *output_codec          = NULL;
    int error;
    /** 创建一个输出文件,稍后用于写入 */
    if ((error = avio_open(&output_io_context, filename,
                           AVIO_FLAG_WRITE)) < 0) {
        fprintf(stderr, "Could not open output file '%s' (error '%s')\n",
                filename, av_err2str(error));
        return error;
    }
    /** 为格式创建一个格式上下文 */
    if (!(*output_format_context = avformat_alloc_context())) {
        fprintf(stderr, "Could not allocate output format context\n");
        return AVERROR(ENOMEM);
    }
    /** 将输出文件(指针)与容器格式上下文关联 */
    (*output_format_context)->pb = output_io_context;
    /** 根据文件扩展名猜测所需的容器格式 */
    if (!((*output_format_context)->oformat = av_guess_format(NULL, filename,
                                                              NULL))) {
        fprintf(stderr, "Could not find output file format\n");
        goto cleanup;
    }
    av_strlcpy((*output_format_context)->filename, filename,
               sizeof((*output_format_context)->filename));
    /** 按编码器名称查找要使用的编码器 */
    if (!(output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC))) {
        fprintf(stderr, "Could not find an AAC encoder.\n");
        goto cleanup;
    }
    /** 在输出文件容器中创建一个新的音频流 */
    if (!(stream = avformat_new_stream(*output_format_context, NULL))) {
        fprintf(stderr, "Could not create new stream\n");
        error = AVERROR(ENOMEM);
        goto cleanup;
    }
    avctx = avcodec_alloc_context3(output_codec);
    if (!avctx) {
        fprintf(stderr, "Could not allocate an encoding context\n");
        error = AVERROR(ENOMEM);
        goto cleanup;
    }
    /**
     * 设置基础编码器参数
     * 输入文件的采样率用于避免采样率转换
     */
    avctx->channels       = OUTPUT_CHANNELS;
    avctx->channel_layout = av_get_default_channel_layout(OUTPUT_CHANNELS);
    avctx->sample_rate    = input_codec_context->sample_rate;
    avctx->sample_fmt     = output_codec->sample_fmts[0];
    avctx->bit_rate       = OUTPUT_BIT_RATE;
    /** 允许使用实验性AAC编码器 */
    avctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
    /** 为容器设置采样率 */
    stream->time_base.den = input_codec_context->sample_rate;
    stream->time_base.num = 1;
    /**
     * 一些容器格式 (像 MP4) 要求设置全局头部
     * 标记编码器使其相应运行
     */
    if ((*output_format_context)->oformat->flags & AVFMT_GLOBALHEADER)
        avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    /** 为音频流打开编码器以至于稍后用它 */
    if ((error = avcodec_open2(avctx, output_codec, NULL)) < 0) {
        fprintf(stderr, "Could not open output codec (error '%s')\n",
                av_err2str(error));
        goto cleanup;
    }
    // 设置编码器参数
    error = avcodec_parameters_from_context(stream->codecpar, avctx);
    if (error < 0) {
        fprintf(stderr, "Could not initialize stream parameters\n");
        goto cleanup;
    }
    /** 保存编码器上下文以便于以后访问 */
    *output_codec_context = avctx;
    return 0;
cleanup:
    avcodec_free_context(&avctx);
    avio_closep(&(*output_format_context)->pb);
    avformat_free_context(*output_format_context);
    *output_format_context = NULL;
    return error < 0 ? error : AVERROR_EXIT;
}
/** 初始化一个packet以便一会读取或写入 */
static void init_packet(AVPacket *packet)
{
    av_init_packet(packet);
    /**设置包的数据为空和大小为0以便后面被识别 */
    packet->data = NULL;
    packet->size = 0;
}
/** 初始化读取文件的一帧 */
static int init_input_frame(AVFrame **frame)
{
    if (!(*frame = av_frame_alloc())) {
        fprintf(stderr, "Could not allocate input frame\n");
        return AVERROR(ENOMEM);
    }
    return 0;
}
/**
 * 根据输入和输出编码器设置初始化音频重采样器
 * 如果输入和输出的格式不同,则需要进行转换
 * libswresample会处理这个问题,但需要初始化
 */
static int init_resampler(AVCodecContext *input_codec_context,
                          AVCodecContext *output_codec_context,
                          SwrContext **resample_context)
{
        int error;
        /**
         * 为转换创建一个重采样器上下文
         * 设置转换参数
         * 基于默认通道式初始化通道布局
         * 为简单起见,假设(有时他们不能正确检查
         * 被解复用器或解码器).
         */
        *resample_context = swr_alloc_set_opts(NULL,
                                              av_get_default_channel_layout(output_codec_context->channels),
                                              output_codec_context->sample_fmt,
                                              output_codec_context->sample_rate,
                                              av_get_default_channel_layout(input_codec_context->channels),
                                              input_codec_context->sample_fmt,
                                              input_codec_context->sample_rate,
                                              0, NULL);
        if (!*resample_context) {
            fprintf(stderr, "Could not allocate resample context\n");
            return AVERROR(ENOMEM);
        }
        /**
        * 执行健全性检查,以便转换的样本数不大于要转换的样本数
        * 如果采样率不同,则必须以不同的方式处理此情况
        */
        av_assert0(output_codec_context->sample_rate == input_codec_context->sample_rate);
        /** 使用指定的参数打开重采样器 */
        if ((error = swr_init(*resample_context)) < 0) {
            fprintf(stderr, "Could not open resample context\n");
            swr_free(resample_context);
            return error;
        }
    return 0;
}
/** 初始化FIFO缓存区用于被编码的音频样本 */
static int init_fifo(AVAudioFifo **fifo, AVCodecContext *output_codec_context)
{
    /** 基于特殊输出格式创建IFIO缓存区 */
    if (!(*fifo = av_audio_fifo_alloc(output_codec_context->sample_fmt,
                                      output_codec_context->channels, 1))) {
        fprintf(stderr, "Could not allocate FIFO\n");
        return AVERROR(ENOMEM);
    }
    return 0;
}
/** 在输出文件上下文写入头部 */
static int write_output_file_header(AVFormatContext *output_format_context)
{
    int error;
    if ((error = avformat_write_header(output_format_context, NULL)) < 0) {
        fprintf(stderr, "Could not write output file header (error '%s')\n",
                av_err2str(error));
        return error;
    }
    return 0;
}
/** 从输入文件中解码一帧音频数据 */
static int decode_audio_frame(AVFrame *frame,
                              AVFormatContext *input_format_context,
                              AVCodecContext *input_codec_context,
                              int *data_present, int *finished)
{
    /** Packet 用于临时存储 */
    AVPacket input_packet;
    int error;
    init_packet(&input_packet);
    /** 从输入文件中读取到的一帧数据转入到临时Packet中 */
    if ((error = av_read_frame(input_format_context, &input_packet)) < 0) {
        /** 如果我们在文件的末尾,刷新下面的解码器 */
        if (error == AVERROR_EOF)
            *finished = 1;
        else {
            fprintf(stderr, "Could not read frame (error '%s')\n",
                    av_err2str(error));
            return error;
        }
    }
    /**
     * 解码存储在临时Packet中的音频帧
     * 这个输入音频流解码器是用来干这活儿的
     * 如果我们在文件的末尾,将一个空包传递给解码器以刷新它
     */
    if ((error = avcodec_decode_audio4(input_codec_context, frame,
                                       data_present, &input_packet)) < 0) {
        fprintf(stderr, "Could not decode frame (error '%s')\n",
                av_err2str(error));
        av_packet_unref(&input_packet);
        return error;
    }
    /**
     * 如果解码器没有被完全清空,则我们没有完成
     * 所以这个方法必须被再次调用一遍
     */
    if (*finished && *data_present)
        *finished = 0;
    av_packet_unref(&input_packet);
    return 0;
}
/**
 * 为特殊音频采样率数初始化一个临时存储
 * 由于不同的格式,转换器需要要一个临时存储
 * 要分配的音频样本数以帧大小指定
 */
static int init_converted_samples(uint8_t ***converted_input_samples,
                                  AVCodecContext *output_codec_context,
                                  int frame_size)
{
    int error;
    /**
     * 分配尽可能多的指针,因为有音频通道
     * 每个指针稍后将指向相应频道的音频样本(尽管对于交错格式可能为空)
     */
    if (!(*converted_input_samples = calloc(output_codec_context->channels,
                                            sizeof(**converted_input_samples)))) {
        fprintf(stderr, "Could not allocate converted input sample pointers\n");
        return AVERROR(ENOMEM);
    }
    /**
     * 为方便起见,为一个连续块中所有通道的样本分配内存
     */
    if ((error = av_samples_alloc(*converted_input_samples, NULL,
                                  output_codec_context->channels,
                                  frame_size,
                                  output_codec_context->sample_fmt, 0)) < 0) {
        fprintf(stderr,
                "Could not allocate converted input samples (error '%s')\n",
                av_err2str(error));
        av_freep(&(*converted_input_samples)[0]);
        free(*converted_input_samples);
        return error;
    }
    return 0;
}
/**
 * 将输入音频样本转换为输出样本格式
 * 转换以每帧为基础进行,其大小由帧大小指定
 */
static int convert_samples(const uint8_t **input_data,
                           uint8_t **converted_data, const int frame_size,
                           SwrContext *resample_context)
{
    int error;
    /** 用重采样器转换采样 */
    if ((error = swr_convert(resample_context,
                             converted_data, frame_size,
                             input_data    , frame_size)) < 0) {
        fprintf(stderr, "Could not convert input samples (error '%s')\n",
                av_err2str(error));
        return error;
    }
    return 0;
}
/** 将转换后的输入音频样本添加到FIFO缓冲区,以便以后处理 */
static int add_samples_to_fifo(AVAudioFifo *fifo,
                               uint8_t **converted_input_samples,
                               const int frame_size)
{
    int error;
    /**
     * 确保这个FIFO足够大,因为它要保持新的和旧的采样
     */
    if ((error = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + frame_size)) < 0) {
        fprintf(stderr, "Could not reallocate FIFO\n");
        return error;
    }
    /** 保存新采样到FIFO缓存区 */
    if (av_audio_fifo_write(fifo, (void **)converted_input_samples,
                            frame_size) < frame_size) {
        fprintf(stderr, "Could not write data to FIFO\n");
        return AVERROR_EXIT;
    }
    return 0;
}
/**
 * 从输入文件中读取一个音频帧,解码、转换并将其存储在FIFO缓冲区中
 */
static int read_decode_convert_and_store(AVAudioFifo *fifo,
                                         AVFormatContext *input_format_context,
                                         AVCodecContext *input_codec_context,
                                         AVCodecContext *output_codec_context,
                                         SwrContext *resampler_context,
                                         int *finished)
{
    /** 从文件读取的帧的输入样本的临时存储。*/
    AVFrame *input_frame = NULL;
    /** 转换输入采样的临时存储 */
    uint8_t **converted_input_samples = NULL;
    int data_present;
    int ret = AVERROR_EXIT;
    /** 为一帧输入帧初始化临时存储 */
    if (init_input_frame(&input_frame))
        goto cleanup;
    /** 解码一帧的音频样本 */
    if (decode_audio_frame(input_frame, input_format_context,
                           input_codec_context, &data_present, finished))
        goto cleanup;
    /**
     * 如果到文件结尾而且没有更多的样本(在解码器中那些延迟的样本)
     * 我们实际上结束了
     * 不得将此视为错误
     */
    if (*finished && !data_present) {
        ret = 0;
        goto cleanup;
    }
    /** 如果这是解码数据, 则进行转换并保存 */
    if (data_present) {
        /** 为转换后的输入帧初始化存储 */
        if (init_converted_samples(&converted_input_samples, output_codec_context,
                                   input_frame->nb_samples))
            goto cleanup;
        /**
         * 将这个输入采样转换到期望的输出采样格式
         * 需要由 converted_input_samples提供临时变量
         */
        if (convert_samples((const uint8_t**)input_frame->extended_data, converted_input_samples,
                            input_frame->nb_samples, resampler_context))
            goto cleanup;
        /** 为后面的程序,将转换后的输入帧采样添加到FIFO缓存中 */
        if (add_samples_to_fifo(fifo, converted_input_samples,
                                input_frame->nb_samples))
            goto cleanup;
        ret = 0;
    }
    ret = 0;
cleanup:
    if (converted_input_samples) {
        av_freep(&converted_input_samples[0]);
        free(converted_input_samples);
    }
    av_frame_free(&input_frame);
    return ret;
}
/**
 * 为写入到输出文件,初始化一帧输入帧
 * 这帧应该刚好是样本大小
 */
static int init_output_frame(AVFrame **frame,
                             AVCodecContext *output_codec_context,
                             int frame_size)
{
    int error;
    /** 创建一帧存储音频样本 */
    if (!(*frame = av_frame_alloc())) {
        fprintf(stderr, "Could not allocate output frame\n");
        return AVERROR_EXIT;
    }
    /**
     * 设置帧的参数,特别是大小和格式.
     * av_frame_get_buffer需要用来创建音频帧采样
     * 为简单起见,假如默认通道布局基于通道数
     */
    (*frame)->nb_samples     = frame_size;
    (*frame)->channel_layout = output_codec_context->channel_layout;
    (*frame)->format         = output_codec_context->sample_fmt;
    (*frame)->sample_rate    = output_codec_context->sample_rate;
    /**
     * 为新创建的帧创建样本, This call will make
     * 这个调用后确保可以容纳指定数量的样本
     */
    if ((error = av_frame_get_buffer(*frame, 0)) < 0) {
        fprintf(stderr, "Could not allocate output frame samples (error '%s')\n",
                av_err2str(error));
        av_frame_free(frame);
        return error;
    }
    return 0;
}
/** 音频帧全局时间戳 */
static int64_t pts = 0;
/** 将一帧音频编码到输出文件 */
static int encode_audio_frame(AVFrame *frame,
                              AVFormatContext *output_format_context,
                              AVCodecContext *output_codec_context,
                              int *data_present)
{
    /** 用于临时存储的Packet */
    AVPacket output_packet;
    int error;
    init_packet(&output_packet);
    /** 根据容器的采样率设置时间戳. */
    if (frame) {
        frame->pts = pts;
        pts += frame->nb_samples;
    }
    /**
     * 编码音频帧并存储到临时Packet
     * 输出音频流编码器就是干这玩意儿的.
     */
    if ((error = avcodec_encode_audio2(output_codec_context, &output_packet,
                                       frame, data_present)) < 0) {
        fprintf(stderr, "Could not encode frame (error '%s')\n",
                av_err2str(error));
        av_packet_unref(&output_packet);
        return error;
    }
    /** Write one audio frame from the temporary packet to the output file. */
    if (*data_present) {
        if ((error = av_write_frame(output_format_context, &output_packet)) < 0) {
            fprintf(stderr, "Could not write frame (error '%s')\n",
                    av_err2str(error));
            av_packet_unref(&output_packet);
            return error;
        }
        av_packet_unref(&output_packet);
    }
    return 0;
}
/**
 * 从FIFO缓存区中读取一帧音频数据并进行编码,然后写入到输出文件中
 */
static int load_encode_and_write(AVAudioFifo *fifo,
                                 AVFormatContext *output_format_context,
                                 AVCodecContext *output_codec_context)
{
    /** 用于写到文件中的输出帧采样的临时存储 */
    AVFrame *output_frame;
    /**
     * 使用每帧可能的最大采样数
     * 如果FIFO缓冲区中的帧大小小于最大可能帧大小,则使用此数字。否则,请使用可能的最大帧大小
     */
    const int frame_size = FFMIN(av_audio_fifo_size(fifo),
                                 output_codec_context->frame_size);
    int data_written;
    /** 为一帧输出帧初始化临时变量 */
    if (init_output_frame(&output_frame, output_codec_context, frame_size))
        return AVERROR_EXIT;
    /**
     * 根据需要从FIFO缓冲区读取尽可能多的样本以填充帧。
     * 样本临时存储在帧中
     */
    if (av_audio_fifo_read(fifo, (void **)output_frame->data, frame_size) < frame_size) {
        fprintf(stderr, "Could not read data from FIFO\n");
        av_frame_free(&output_frame);
        return AVERROR_EXIT;
    }
    /** 将采样编码成一帧音频数据 */
    if (encode_audio_frame(output_frame, output_format_context,
                           output_codec_context, &data_written)) {
        av_frame_free(&output_frame);
        return AVERROR_EXIT;
    }
    av_frame_free(&output_frame);
    return 0;
}
/** 写入输出文件上下文的尾部r. */
static int write_output_file_trailer(AVFormatContext *output_format_context)
{
    int error;
    if ((error = av_write_trailer(output_format_context)) < 0) {
        fprintf(stderr, "Could not write output file trailer (error '%s')\n",
                av_err2str(error));
        return error;
    }
    return 0;
}
/** 将音频文件转换为MP4容器中的AAC文件 */
int main(int argc, char **argv)
{
    AVFormatContext *input_format_context = NULL, *output_format_context = NULL;
    AVCodecContext *input_codec_context = NULL, *output_codec_context = NULL;
    SwrContext *resample_context = NULL;
    AVAudioFifo *fifo = NULL;
    int ret = AVERROR_EXIT;
    if (argc < 3) {
        fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
        exit(1);
    }
    /** 注册所有编解码和格式,以便可以使用 */
    av_register_all();
    /** 打开输入文件以备读取 */
    if (open_input_file(argv[1], &input_format_context,
                        &input_codec_context))
        goto cleanup;
    /** 创建输出文件以备写入 */
    if (open_output_file(argv[2], input_codec_context,
                         &output_format_context, &output_codec_context))
        goto cleanup;
    /** 初始化用于转换音频的重采样器 */
    if (init_resampler(input_codec_context, output_codec_context,
                       &resample_context))
        goto cleanup;
    /** 初始化用于存储编码音频帧采样的FIFO缓存区 */
    if (init_fifo(&fifo, output_codec_context))
        goto cleanup;
    /** 写输出文件上下文的头部 */
    if (write_output_file_header(output_format_context))
        goto cleanup;
    /**
     * 循环,只要我们有输入样本读取或输出样本写入
     * 如果两个都没有则结束
     */
    while (1) {
        /** 使用编码器的期望帧大小 */
        const int output_frame_size = output_codec_context->frame_size;
        int finished                = 0;
        /**
         * 确保有一帧采样数据在FIFO缓存区一遍编码器正常工作  
         * 因为编码器和和解码器帧数据大小可能不同,我们需要一个FIFO缓存区去保存足够多的
         * 输入帧采样数据,直到它能够凑够一帧输出数据
         */
        while (av_audio_fifo_size(fifo) < output_frame_size) {
            /**
             * 编码一帧音频样本数据,转换到输出采样格式,并且放到FIFO缓存区中
             */
            if (read_decode_convert_and_store(fifo, input_format_context,
                                              input_codec_context,
                                              output_codec_context,
                                              resample_context, &finished))
                goto cleanup;
            /**
             * 如果到了文件结束,继续编码这些需要编码的音频采样到输出文件中
             */
            if (finished)
                break;
        }
        /**
         * 如果我们有足够多的样本给编码器,我们编码他们If we have enough samples for the encoder, we encode them.
         * 在文件结尾,我们将剩余的样本传递给编码器。
         */
        while (av_audio_fifo_size(fifo) >= output_frame_size ||
               (finished && av_audio_fifo_size(fifo) > 0))
            /**
             * 从FIFO缓存区中获取一帧音频采样数据Take one frame worth of audio samples from the FIFO buffer,
             * 编码并写入到输出文件
             */
            if (load_encode_and_write(fifo, output_format_context,
                                      output_codec_context))
                goto cleanup;
        /**
         * 如果到了文件结尾,并且所有帧已经编码完毕,则退出循环
         */
        if (finished) {
            int data_written;
            /** 如果还有延时帧,清空编码器 */
            do {
                if (encode_audio_frame(NULL, output_format_context,
                                       output_codec_context, &data_written))
                    goto cleanup;
            } while (data_written);
            break;
        }
    }
    /** 为输出文件上下文写入尾部 */
    if (write_output_file_trailer(output_format_context))
        goto cleanup;
    ret = 0;
cleanup:
    if (fifo)
        av_audio_fifo_free(fifo);
    swr_free(&resample_context);
    if (output_codec_context)
        avcodec_free_context(&output_codec_context);
    if (output_format_context) {
        avio_closep(&output_format_context->pb);
        avformat_free_context(output_format_context);
    }
    if (input_codec_context)
        avcodec_free_context(&input_codec_context);
    if (input_format_context)
        avformat_close_input(&input_format_context);
    return ret;
}

源码地址

http://ffmpeg.org/doxygen/3.4/transcode_aac_8c-example.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值