ffplay源码分析6-音频重采样

本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10312713.html

ffplay 是 FFmpeg 工程自带的简单播放器,使用 FFmpeg 提供的解码器和 SDL 库进行视频播放。本文基于 FFmpeg 工程 8.0 版本进行分析,其中 ffplay 源码清单如下:
https://github.com/FFmpeg/FFmpeg/blob/n8.0/fftools/ffplay.c

在尝试分析源码前,可先阅读如下参考文章作为铺垫:
[1]. 雷霄骅,视音频编解码技术零基础学习方法
[2]. 视频编解码基础概念
[3]. 色彩空间与像素格式
[4]. 音频参数解析
[5]. FFmpeg基础概念

“ffplay源码分析”系列文章如下:
[1]. ffplay源码分析1-概述
[2]. ffplay源码分析2-数据结构
[3]. ffplay源码分析3-代码框架
[4]. ffplay源码分析4-音视频同步
[5]. ffplay源码分析5-图像格式转换
[6]. ffplay源码分析6-音频重采样
[7]. ffplay源码分析7-播放控制

6. 音频重采样

FFmpeg 解码得到的音频帧的格式未必能被 SDL 支持,在这种情况下,需要进行音频重采样,即将音频帧格式转换为 SDL 支持的音频格式,否则是无法正常播放的。

音频重采样涉及两个步骤:

  1. 打开音频设备时进行的准备工作:确定 SDL 支持的音频格式,作为后期音频重采样的目标格式。
  2. 音频播放线程中,取出音频帧后,若有需要(音频帧格式与 SDL 支持音频格式不匹配)则进行重采样,否则直接输出。

6.1 音频格式和数据结构

音频重采样过程中涉及很多音频参数,这些音频参数涉及到 FFmpeg 中音频存储的基础概念,需要先了解一下。

6.1.1 音频 planar 和 packed 格式

音频采样格式有两大类型:planar 和 packed,假设一个双声道音频文件,一个左声道采样点记作 L,一个右声道采样点记作 R,则:

planar 存储格式:

-------------------------------
L L L L L L L L L L L L L L ...
-------------------------------
R R R R R R R R R R R R R R ...
-------------------------------

packed 存储格式

-------------------------------
L R L R L R L R L R L R L R ...
-------------------------------

在这两种采样类型下,又细分多种采样格式,如 AV_SAMPLE_FMT_S16、AV_SAMPLE_FMT_S16P 等,注意 SDL 2.0 目前不支持 planar 格式。

6.1.2 SDL 中的音频数据结构

6.1.2.1 SDL_AudioSpec

SDL 中音频参数的数据结构定义如下:

/**
 *  The calculated values in this structure are calculated by SDL_OpenAudio().
 *
 *  For multi-channel audio, the default SDL channel mapping is:
 *  2:  FL FR                       (stereo)
 *  3:  FL FR LFE                   (2.1 surround)
 *  4:  FL FR BL BR                 (quad)
 *  5:  FL FR FC BL BR              (quad + center)
 *  6:  FL FR FC LFE SL SR          (5.1 surround - last two can also be BL BR)
 *  7:  FL FR FC LFE BC SL SR       (6.1 surround)
 *  8:  FL FR FC LFE BL BR SL SR    (7.1 surround)
 */
typedef struct SDL_AudioSpec
{
    int freq;                   /**< DSP frequency -- samples per second */
    SDL_AudioFormat format;     /**< Audio data format */
    Uint8 channels;             /**< Number of channels: 1 mono, 2 stereo */
    Uint8 silence;              /**< Audio buffer silence value (calculated) */
    Uint16 samples;             /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */
    Uint16 padding;             /**< Necessary for some compile environments */
    Uint32 size;                /**< Audio buffer size in bytes (calculated) */
    SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */
    void *userdata;             /**< Userdata passed to callback (ignored for NULL callbacks). */
} SDL_AudioSpec;

6.1.2.2 SDL_AudioFormat

SDL 中的音频格式定义如下:

/**
 *  \brief Audio format flags.
 *
 *  These are what the 16 bits in SDL_AudioFormat currently mean...
 *  (Unspecified bits are always zero).
 *
 *  \verbatim
    ++-----------------------sample is signed if set
    ||
    ||       ++-----------sample is bigendian if set
    ||       ||
    ||       ||          ++---sample is float if set
    ||       ||          ||
    ||       ||          || +---sample bit size---+
    ||       ||          || |                     |
    15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
    \endverbatim
 *
 *  There are macros in SDL 2.0 and later to query these bits.
 */
typedef Uint16 SDL_AudioFormat;

/**
 *  \name Audio format flags
 *
 *  Defaults to LSB byte order.
 */
/* @{ */
#define AUDIO_U8        0x0008  /**< Unsigned 8-bit samples */
#define AUDIO_S8        0x8008  /**< Signed 8-bit samples */
#define AUDIO_U16LSB    0x0010  /**< Unsigned 16-bit samples */
#define AUDIO_S16LSB    0x8010  /**< Signed 16-bit samples */
#define AUDIO_U16MSB    0x1010  /**< As above, but big-endian byte order */
#define AUDIO_S16MSB    0x9010  /**< As above, but big-endian byte order */
#define AUDIO_U16       AUDIO_U16LSB
#define AUDIO_S16       AUDIO_S16LSB
/* @} */

6.1.3 FFmpeg 中的音频数据结构

对于原始视频帧,FFmpeg 中定义了像素格式 (pixel formats) 来描述视频帧在内存中的所有布局细节信息。而对于原始音频帧,FFmpeg 中定义了声道布局 (channel layout) 和采样格式 (sample format) 来确定音频帧的内存布局细节。

6.1.3.1 AudioParams

FFmpeg 中音频参数的数据结构定义如下 (非底层数据结构,定义在 ffplay.c 中):

typedef struct AudioParams {
    int freq;
    AVChannelLayout ch_layout;
    enum AVSampleFormat fmt;
    int frame_size;
    int bytes_per_sec;
} AudioParams;

6.1.3.2 AVSampleFormat

FFmpeg 中的音频采样格式定义如下 (libavutil/samplefmt.h):

/**
 * Audio sample formats
 *
 * - The data described by the sample format is always in native-endian order.
 *   Sample values can be expressed by native C types, hence the lack of a signed
 *   24-bit sample format even though it is a common raw audio data format.
 *
 * - The floating-point formats are based on full volume being in the range
 *   [-1.0, 1.0]. Any values outside this range are beyond full volume level.
 *
 * - The data layout as used in av_samples_fill_arrays() and elsewhere in FFmpeg
 *   (such as AVFrame in libavcodec) is as follows:
 *
 * @par
 * For planar sample formats, each audio channel is in a separate data plane,
 * and linesize is the buffer size, in bytes, for a single plane. All data
 * planes must be the same size. For packed sample formats, only the first data
 * plane is used, and samples for each channel are interleaved. In this case,
 * linesize is the buffer size, in bytes, for the 1 plane.
 *
 */
enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

6.1.3.3 AVChannelLayout

音频 channel_layout 表示音频声道布局,每种 channel_layout 用一个 uint64_t 的整数表示,其中每 bit 代表一个特定的声道。

本节数据结构定义于 libavutil/channel_layout.h:

struct AVChannelLayout

/**
 * An AVChannelLayout holds information about the channel layout of audio data.
 *
 * A channel layout here is defined as a set of channels ordered in a specific
 * way (unless the channel order is AV_CHANNEL_ORDER_UNSPEC, in which case an
 * AVChannelLayout carries only the channel count).
 * All orders may be treated as if they were AV_CHANNEL_ORDER_UNSPEC by
 * ignoring everything but the channel count, as long as av_channel_layout_check()
 * considers they are valid.
 *
 * Unlike most structures in FFmpeg, sizeof(AVChannelLayout) is a part of the
 * public ABI and may be used by the caller. E.g. it may be allocated on stack
 * or embedded in caller-defined structs.
 *
 * AVChannelLayout can be initialized as follows:
 * - default initialization with {0}, followed by setting all used fields
 *   correctly;
 * - by assigning one of the predefined AV_CHANNEL_LAYOUT_* initializers;
 * - with a constructor function, such as av_channel_layout_default(),
 *   av_channel_layout_from_mask() or av_channel_layout_from_string().
 *
 * The channel layout must be unitialized with av_channel_layout_uninit()
 *
 * Copying an AVChannelLayout via assigning is forbidden,
 * av_channel_layout_copy() must be used instead (and its return value should
 * be checked)
 *
 * No new fields may be added to it without a major version bump, except for
 * new elements of the union fitting in sizeof(uint64_t).
 */
typedef struct AVChannelLayout {
    /**
     * Channel order used in this layout.
     * This is a mandatory field.
     */
    enum AVChannelOrder order;

    /**
     * Number of channels in this layout. Mandatory field.
     */
    int nb_channels;

    /**
     * Details about which channels are present in this layout.
     * For AV_CHANNEL_ORDER_UNSPEC, this field is undefined and must not be
     * used.
     */
    union {
        /**
         * This member must be used for AV_CHANNEL_ORDER_NATIVE, and may be used
         * for AV_CHANNEL_ORDER_AMBISONIC to signal non-diegetic channels.
         * It is a bitmask, where the position of each set bit means that the
         * AVChannel with the corresponding value is present.
         *
         * I.e. when (mask & (1 << AV_CHAN_FOO)) is non-zero, then AV_CHAN_FOO
         * is present in the layout. Otherwise it is not present.
         *
         * @note when a channel layout using a bitmask is constructed or
         * modified manually (i.e.  not using any of the av_channel_layout_*
         * functions), the code doing it must ensure that the number of set bits
         * is equal to nb_channels.
         */
        uint64_t mask;
        /**
         * This member must be used when the channel order is
         * AV_CHANNEL_ORDER_CUSTOM. It is a nb_channels-sized array, with each
         * element signalling the presence of the AVChannel with the
         * corresponding value in map[i].id.
         *
         * I.e. when map[i].id is equal to AV_CHAN_FOO, then AV_CH_FOO is the
         * i-th channel in the audio data.
         *
         * When map[i].id is in the range between AV_CHAN_AMBISONIC_BASE and
         * AV_CHAN_AMBISONIC_END (inclusive), the channel contains an ambisonic
         * component with ACN index (as defined above)
         * n = map[i].id - AV_CHAN_AMBISONIC_BASE.
         *
         * map[i].name may be filled with a 0-terminated string, in which case
         * it will be used for the purpose of identifying the channel with the
         * convenience functions below. Otherise it must be zeroed.
         */
        AVChannelCustom *map;
    } u;

    /**
     * For some private data of the user.
     */
    void *opaque;
} AVChannelLayout;

AVChannelLayout 结构体包含 channel_layout 的所有信息:声道顺序,声道数量,和 uint64_t 的底层声道布局定义,AVChannelLayout 是 FFmpeg 中 channel_layout 的通用数据结构,而 AVChannelLayout.u.mask 则是 FFmpeg 中 channel_layout 最底层定义 (即前面提到的 uint64_t 型 channel_layout )。

enum AVChannel

enum AVChannel {
    ///< Invalid channel index
    AV_CHAN_NONE = -1,
    AV_CHAN_FRONT_LEFT,
    AV_CHAN_FRONT_RIGHT,
    AV_CHAN_FRONT_CENTER,
    AV_CHAN_LOW_FREQUENCY,
    AV_CHAN_BACK_LEFT,
    AV_CHAN_BACK_RIGHT,
    ...
};

定义 enum 类型的 channel,其实是某个声道的索引值,比如左声道 (FRONT_LEFT) 是 0、右声道 (FRONT_RIGHT) 是 1,FRONT_CENTER 声道是2,……

channel masks:

/**
 * @defgroup channel_masks Audio channel masks
 *
 * A channel layout is a 64-bits integer with a bit set for every channel.
 * The number of bits set must be equal to the number of channels.
 * The value 0 means that the channel layout is not known.
 * @note this data structure is not powerful enough to handle channels
 * combinations that have the same channel multiple times, such as
 * dual-mono.
 *
 * @{
 */
#define AV_CH_FRONT_LEFT             (1ULL << AV_CHAN_FRONT_LEFT           )
#define AV_CH_FRONT_RIGHT            (1ULL << AV_CHAN_FRONT_RIGHT          )
#define AV_CH_FRONT_CENTER           (1ULL << AV_CHAN_FRONT_CENTER         )
#define AV_CH_LOW_FREQUENCY          (1ULL << AV_CHAN_LOW_FREQUENCY        )
#define AV_CH_BACK_LEFT              (1ULL << AV_CHAN_BACK_LEFT            )
#define AV_CH_BACK_RIGHT             (1ULL << AV_CHAN_BACK_RIGHT           )
...

定义某一个 channel 的 mask,比如左声道 (AV_CHAN_FRONT_LEFT) 在 uint64_t 型 channel_layout 的第 0 bit,右声道(AV_CHAN_FRONT_RIGHT) 在第 1 bit,AV_CHAN_FRONT_CENTER 声道在第 2 bit,……

uint64_t 类型的 channel layouts

/**
 * @}
 * @defgroup channel_mask_c Audio channel layouts
 * @{
 * */
#define AV_CH_LAYOUT_MONO              (AV_CH_FRONT_CENTER)
#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)
#define AV_CH_LAYOUT_2POINT1           (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_2_1               (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)
...
#define AV_CH_LAYOUT_QUAD              (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
...

定义了 uint64_t 类型的 channel_layout,比如单声道 (LAYOUT_MONO) 只有 FRONT_CENTER 一个声道,立体声 (LAYOUT_STEREO) 使用左声道 (FRONT_LEFT) 和右声道 (FRONT_RIGHT) 两个声道,四声道 (LAYOUT_QUAD) 则使用左声道 (FRONT_LEFT)、右声道( FRONT_RIGHT)、后左声道 (BACK_LEFT) 和后右声道 (BACK_RIGHT) 共四个声道。

AVChannelLayout 类型的 channel_layout:

/**
 * Macro to define native channel layouts
 *
 * @note This doesn't use designated initializers for compatibility with C++ 17 and older.
 */
#define AV_CHANNEL_LAYOUT_MASK(nb, m) \
    { /* .order */ AV_CHANNEL_ORDER_NATIVE, \
      /* .nb_channels */  (nb), \
      /* .u.mask */ { m }, \
      /* .opaque */ NULL }

/**
 * @name Common pre-defined channel layouts
 * @{
 */
#define AV_CHANNEL_LAYOUT_MONO              AV_CHANNEL_LAYOUT_MASK(1,  AV_CH_LAYOUT_MONO)
#define AV_CHANNEL_LAYOUT_STEREO            AV_CHANNEL_LAYOUT_MASK(2,  AV_CH_LAYOUT_STEREO)
#define AV_CHANNEL_LAYOUT_2POINT1           AV_CHANNEL_LAYOUT_MASK(3,  AV_CH_LAYOUT_2POINT1)
#define AV_CHANNEL_LAYOUT_2_1               AV_CHANNEL_LAYOUT_MASK(3,  AV_CH_LAYOUT_2_1)
...

通过带参宏定义了 AVChannelLayout 类型的 channel_layout,AVChannelLayout 的每个成员通过宏参数赋值,比如立体声 (AV_CHANNEL_LAYOUT_STEREO),其声道数 .nb_channels 值为 2,其声道布局 .u.mask 值为 AV_CH_LAYOUT_STEREO。

6.1.3.4 channel_layout 命名

本节代码取自 libavutil/channel_layout.c 文件:

struct channel_layout_name {
    const char *name;
    AVChannelLayout layout;
};

static const struct channel_layout_name channel_layout_map[] = {
    { "mono",           AV_CHANNEL_LAYOUT_MONO                },
    { "stereo",         AV_CHANNEL_LAYOUT_STEREO              },
    { "2.1",            AV_CHANNEL_LAYOUT_2POINT1             },
    { "3.0",            AV_CHANNEL_LAYOUT_SURROUND            },
    { "3.0(back)",      AV_CHANNEL_LAYOUT_2_1                 },
    { "4.0",            AV_CHANNEL_LAYOUT_4POINT0             },
    { "quad",           AV_CHANNEL_LAYOUT_QUAD                },
    { "quad(side)",     AV_CHANNEL_LAYOUT_2_2                 },
    { "3.1",            AV_CHANNEL_LAYOUT_3POINT1             },
    { "5.0",            AV_CHANNEL_LAYOUT_5POINT0_BACK        },
    { "5.0(side)",      AV_CHANNEL_LAYOUT_5POINT0             },
    { "4.1",            AV_CHANNEL_LAYOUT_4POINT1             },
    { "5.1",            AV_CHANNEL_LAYOUT_5POINT1_BACK        },
    { "5.1(side)",      AV_CHANNEL_LAYOUT_5POINT1             },
    { "6.0",            AV_CHANNEL_LAYOUT_6POINT0             },
    { "6.0(front)",     AV_CHANNEL_LAYOUT_6POINT0_FRONT       },
    { "3.1.2",          AV_CHANNEL_LAYOUT_3POINT1POINT2       },
    { "hexagonal",      AV_CHANNEL_LAYOUT_HEXAGONAL           },
    { "6.1",            AV_CHANNEL_LAYOUT_6POINT1             },
    { "6.1(back)",      AV_CHANNEL_LAYOUT_6POINT1_BACK        },
    { "6.1(front)",     AV_CHANNEL_LAYOUT_6POINT1_FRONT       },
    { "7.0",            AV_CHANNEL_LAYOUT_7POINT0             },
    { "7.0(front)",     AV_CHANNEL_LAYOUT_7POINT0_FRONT       },
    { "7.1",            AV_CHANNEL_LAYOUT_7POINT1             },
    { "7.1(wide)",      AV_CHANNEL_LAYOUT_7POINT1_WIDE_BACK   },
    { "7.1(wide-side)", AV_CHANNEL_LAYOUT_7POINT1_WIDE        },
    { "5.1.2",          AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK  },
    { "octagonal",      AV_CHANNEL_LAYOUT_OCTAGONAL           },
    { "cube",           AV_CHANNEL_LAYOUT_CUBE                },
    { "5.1.4",          AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK  },
    { "7.1.2",          AV_CHANNEL_LAYOUT_7POINT1POINT2       },
    { "7.1.4",          AV_CHANNEL_LAYOUT_7POINT1POINT4_BACK  },
    { "7.2.3",          AV_CHANNEL_LAYOUT_7POINT2POINT3       },
    { "9.1.4",          AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK  },
    { "hexadecagonal",  AV_CHANNEL_LAYOUT_HEXADECAGONAL       },
    { "downmix",        AV_CHANNEL_LAYOUT_STEREO_DOWNMIX,     },
    { "22.2",           AV_CHANNEL_LAYOUT_22POINT2,           },
};

上述代码为每个 channel_layout 取了一个名字。这个静态数组 channel_layout_name[] 被各个 av_channel_layout_xxx() 函数使用。

6.2 音频解码和播放过程概览

音频重采样有两种实现方式,一种是通过音频滤镜 (filter) 实现,另一种是直接使用 swr_convert() 函数实现。实际上第一种方式中,音频滤镜的底层也是调用 swr_convert() 实现的音频重采样。

旧版 ffplay 中滤镜是可选功能,代码通过 CONFIG_AVFILTER 宏决定是否使用滤镜。新版 ffplay (也就是 c29e5ab5 这个提交之后),已删除了 CONFIG_AVFILTER 宏,滤镜功能成为必须功能。改动参考如下提交记录:

2023-03-10 c29e5ab5 fftools/ffplay: depend on avfilter

Making lavfi optional adds a lot of complexity for very questionable
gain.

新版 ffplay 删除了旧版中视频播放线程中的图像格式转换代码,只使用视频滤镜做图像格式转换。但是音频不同,音频播放线程中的音频重采样代码却没有删除,也就是解码线程中通过滤镜实现重采样和播放线程中通过 swr_convert() 实现重采样两处代码同时存在,这让音频流的解码和播放过程变得更加复杂。所以我们先在这里把音频解码和播放的过程大概捋一遍,先有一个大概的脉络就不至于迷失在代码细节中,后面各节再补充相关细节。

ffplay音频数据流图

  1. 解复用器 (demuxer) 读取媒体文件,分离音视频流,将音频 packet 存入音频 packet 队列;
  2. 音频解码器 (decoder) 将音频 packet 解码得到音频 frame,然后送入音频滤镜 (filter);
  3. 音频滤镜 (filter) 对音频 frame 进行各种处理,其中包括音频重采样,从滤镜输出的音频 frame 被存入音频 frame 队列;
  4. 播放线程回调 SDL 音频回调函数,通常并不执行重采样过程,直接将音频数据送入声卡缓冲区,声卡驱动会负责音频播放。

在上图“ffplay 音频数据流图”中,解码线程中滤镜输出的帧,会作为播放线程中 swr_convert() 重采样函数的输入帧,滤镜重采样实际上已经把音频帧转换为 SDL 能播放的格式,所以通常播放线程中的重采样过程是不会执行的。什么情况下它会执行呢?如果同步方式是音频同步到视频或者音频同步到外部时钟,这些情况下会在音频播放线程的音视频同步过程中调整音频帧中的声音样本数,这个时候就会执行播放线程中音频重采样过程。而我们最常用的同步方式是视频同步到音频,音频时钟是同步时钟,这种情况下是不会执行播放线程中的重采样过程的。

6.3 解复用线程中的准备工作

解复用线程为音频解码和播放做了几项关键的准备工作:打开音频设备,创建音频解码线程,启动音频播放线程。

6.3.1 打开音频设备流程

音频设备的打开实际是在解复用线程 (read_thread) 中实现的。解复用线程中先打开音频设备(设定音频回调函数供 SDL 音频播放线程回调),然后再创建音频解码线程。基本过程如下(参 3.3.2.3 节):

read_thread()
|-> stream_component_open()
    |-> audio_open()
        |-> SDL_OpenAudioDevice()           // 创建音频播放线程
    |-> decoder_start()                     // 创建音频解码线程
    |-> SDL_PauseAudioDevice(audio_dev, 0)  // 启动音频播放:SDL将开始按需回调SDL音频回调函数

6.3.2 audio_open() 打开音频设备

audio_open() 函数填入期望的音频参数,打开音频设备后,获取的实际音频参数可能和期望的参数有差别,所获取的实际音频参数就是音频重采样的目标参数。实际音频参数被存入输出参数 is->audio_tgt 中,后面音频播放线程使用会此音频参数作为重采样目标参数,进行音频重采样,重采样后的音频数据交由 SDL 播放。

audio_open() 是被解复用线程中的 stream_component_open() 函数调用,如下:

static int stream_component_open(VideoState *is, int stream_index)
{
        /* prepare audio output */
        // 打开音频设备,打开后实际的音频参数会存入输出参数is->audio_tgt中
        // is->audio_tgt是SDL最终播放允许的实际参数
        // is->audio_src是音频frame中的音频参数
        // 例如:SDL2.0不支持planar格式音频,如果frame中音频格式是planar,则将frame音频数据直接送到音频缓冲区是无法正常播放的
        //       此时需要进行音频重采样:将is->audio_src格式转换为is->audio_tgt格式
        if ((ret = audio_open(is, &ch_layout, sample_rate, &is->audio_tgt)) < 0)
            goto fail;
}

可以看到,音频重采样的目标参数通过 audio_open() 函数的第四个参数存入 is->audio_tgt 变量中。

audio_open() 函数实现如下:

static int audio_open(void *opaque, AVChannelLayout *wanted_channel_layout, int wanted_sample_rate, struct AudioParams *audio_hw_params)
{
    SDL_AudioSpec wanted_spec, spec;
    const char *env;
    static const int next_nb_channels[] = {0, 0, 1, 6, 2, 6, 4, 6};
    static const int next_sample_rates[] = {0, 44100, 48000, 96000, 192000};
    int next_sample_rate_idx = FF_ARRAY_ELEMS(next_sample_rates) - 1;
    int wanted_nb_channels = wanted_channel_layout->nb_channels;

    env = SDL_getenv("SDL_AUDIO_CHANNELS");
    if (env) {  // 若环境变量有设置,优先从环境变量取得声道数和声道布局
        wanted_nb_channels = atoi(env);
        av_channel_layout_uninit(wanted_channel_layout);
        av_channel_layout_default(wanted_channel_layout, wanted_nb_channels);
    }
    if (wanted_channel_layout->order != AV_CHANNEL_ORDER_NATIVE) {
        av_channel_layout_uninit(wanted_channel_layout);
        av_channel_layout_default(wanted_channel_layout, wanted_nb_channels);
    }
    // 根据channel_layout获取nb_channels,当传入参数wanted_nb_channels不匹配时,此处会作修正
    wanted_nb_channels = wanted_channel_layout->nb_channels;
    wanted_spec.channels = wanted_nb_channels;  // 声道数
    wanted_spec.freq = wanted_sample_rate;      // 采样率
    if (wanted_spec.freq <= 0 || wanted_spec.channels <= 0) {
        av_log(NULL, AV_LOG_ERROR, "Invalid sample rate or channel count!\n");
        return -1;
    }
    while (next_sample_rate_idx && next_sample_rates[next_sample_rate_idx] >= wanted_spec.freq)
        next_sample_rate_idx--;     // 从采样率数组中找到第一个小于传入参数wanted_sample_rate的值
    wanted_spec.format = AUDIO_S16SYS;          // 采样格式:S表带符号,16是采样深度(位深),SYS表采用系统字节序,这个宏在SDL中定义
    wanted_spec.silence = 0;                    // 静音值
    // wanted_spec.samples是SDL声音缓冲区尺寸,单位是单声道采样点尺寸x声道数
    wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC));
    wanted_spec.callback = sdl_audio_callback;  // 回调函数,若为NULL,则应使用SDL_QueueAudio()机制
    wanted_spec.userdata = opaque;              // 提供给回调函数的参数
    // 打开音频设备并创建SDL音频处理线程。期望的参数是wanted_spec,实际得到的硬件参数是spec
    while (!(audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE))) {
        av_log(NULL, AV_LOG_WARNING, "SDL_OpenAudio (%d channels, %d Hz): %s\n",
               wanted_spec.channels, wanted_spec.freq, SDL_GetError());
        // 如果打开音频设备失败,则尝试用不同的声道数或采样率再次打开音频设备,这里有些奇怪,暂不深究
        wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)];
        if (!wanted_spec.channels) {
            wanted_spec.freq = next_sample_rates[next_sample_rate_idx--];
            wanted_spec.channels = wanted_nb_channels;
            if (!wanted_spec.freq) {
                av_log(NULL, AV_LOG_ERROR,
                       "No more combinations to try, audio open failed\n");
                return -1;
            }
        }
        av_channel_layout_default(wanted_channel_layout, wanted_spec.channels);
    }
    // 检查打开音频设备的实际参数:采样格式
    if (spec.format != AUDIO_S16SYS) {
        av_log(NULL, AV_LOG_ERROR,
               "SDL advised audio format %d is not supported!\n", spec.format);
        return -1;
    }
    // 检查打开音频设备的实际参数:声道数
    if (spec.channels != wanted_spec.channels) {
        av_channel_layout_uninit(wanted_channel_layout);
        av_channel_layout_default(wanted_channel_layout, spec.channels);
        if (wanted_channel_layout->order != AV_CHANNEL_ORDER_NATIVE) {
            av_log(NULL, AV_LOG_ERROR,
                   "SDL advised channel count %d is not supported!\n", spec.channels);
            return -1;
        }
    }

    // wanted_spec是期望的参数,spec是实际的参数,wanted_spec和spec都是SDL中的参数。
    // 此处audio_hw_params是FFmpeg中的参数,输出参数供上级函数使用
    audio_hw_params->fmt = AV_SAMPLE_FMT_S16;
    audio_hw_params->freq = spec.freq;
    if (av_channel_layout_copy(&audio_hw_params->ch_layout, wanted_channel_layout) < 0)
        return -1;
    audio_hw_params->frame_size = av_samples_get_buffer_size(NULL, audio_hw_params->ch_layout.nb_channels, 1, audio_hw_params->fmt, 1);
    audio_hw_params->bytes_per_sec = av_samples_get_buffer_size(NULL, audio_hw_params->ch_layout.nb_channels, audio_hw_params->freq, audio_hw_params->fmt, 1);
    if (audio_hw_params->bytes_per_sec <= 0 || audio_hw_params->frame_size <= 0) {
        av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size failed\n");
        return -1;
    }
    return spec.size;
}

6.3.3 SDL_OpenAudioDevice() 启动音频播放线程

调用 SDL_OpenAudio() 或 SDL_OpenAudioDevice() 打开音频设备并创建音频播放线程,音频播放线程是 SDL 内置线程。SDL_OpenAudioDevice() 函数的输入参数是预期的参数,输出参数是实际参数,实际参数与底层软硬件相关。

SDL 提供两种使音频设备取得音频数据方法:
1). push,SDL 以特定的频率调用回调函数,在回调函数中取得音频数据,此种情况需将 wanted_spec.callback 指向回调函数。
2). pull,用户程序以特定的频率调用 SDL_QueueAudio(),向音频设备提供数据,此种情况 wanted_spec.callback 设置为 NULL。

音频设备打开后处于暂停状态,暂停状态下设备播放静音且 SDL 不启动回调。调用 SDL_PauseAudio(0) 后就取消了暂停状态,SDL 开始启动回调,设备开始正常播放音频。

SDL_OpenAudioDevice() 函数第一个参数为 NULL 时,等价于 SDL_OpenAudio() 函数。

6.4 解码线程中的音频重采样

音频解码线程中使用音频滤镜实现音频重采样。音频解码线程将音频解码、重采样,然后存入音频 frame 队列。音频播放线程从音频队列取出音频帧进行播放。

6.4.1 滤镜的配置

滤镜的配置比使用复杂。使用滤镜实现重采样时,滤镜的源参数是 VideoState.audio_filter_src,目标参数是 VideoState.audio_tgt。在播放线程中,使用 swr_convert() 做重采样时,源参数是 VideoState.is_src,目标参数是 VideoState.audio_tgt,struct VideoState 结构体中定义的这三个音频参数很重要,如下:

typedef struct VideoState {
    ...
    struct AudioParams audio_src;        // 音频播放线程中音频重采样的源参数
    struct AudioParams audio_filter_src; // 音频解码线程中音频滤镜图的源参数
    // 音频解码线程中音频重采样的目标参数,重采样转换:audio_filter_src==>audio_tgt
    // 音频播放线程中音频重采样的目标参数,重采样转换:audio_src==>audio_tgt
    struct AudioParams audio_tgt;
    ...
} VideoState;

6.4.1.1 configure_audio_filters()

滤镜图的概念第 5 节已经讲过,这里不赘述。configure_audio_filters() 用于配置音频滤镜图,代码清单如下:

static int configure_audio_filters(VideoState *is, const char *afilters, int force_output_format)
{
    AVFilterContext *filt_asrc = NULL, *filt_asink = NULL;
    char aresample_swr_opts[512] = "";
    const AVDictionaryEntry *e = NULL;
    AVBPrint bp;
    char asrc_args[256];
    int ret;

    avfilter_graph_free(&is->agraph);
    if (!(is->agraph = avfilter_graph_alloc()))
        return AVERROR(ENOMEM);
    is->agraph->nb_threads = filter_nbthreads;

    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);

    while ((e = av_dict_iterate(swr_opts, e)))
        av_strlcatf(aresample_swr_opts, sizeof(aresample_swr_opts), "%s=%s:", e->key, e->value);
    if (strlen(aresample_swr_opts))
        aresample_swr_opts[strlen(aresample_swr_opts)-1] = '\0';
    av_opt_set(is->agraph, "aresample_swr_opts", aresample_swr_opts, 0);

    av_channel_layout_describe_bprint(&is->audio_filter_src.ch_layout, &bp);

    // 构建abuffer滤镜的参数:来自is->audio_filter_src中的采样率、采样格式、声道布局等信息
    ret = snprintf(asrc_args, sizeof(asrc_args),
                   "sample_rate=%d:sample_fmt=%s:time_base=%d/%d:channel_layout=%s",
                   is->audio_filter_src.freq, av_get_sample_fmt_name(is->audio_filter_src.fmt),
                   1, is->audio_filter_src.freq, bp.str);

    // 创建abuffer滤镜并初始化
    ret = avfilter_graph_create_filter(&filt_asrc,
                                       avfilter_get_by_name("abuffer"), "ffplay_abuffer",
                                       asrc_args, NULL, is->agraph);
    if (ret < 0)
        goto end;

    // 为abuffersink滤镜创建实例:filt_asink(只分配"filter context",而未做初始化)
    filt_asink = avfilter_graph_alloc_filter(is->agraph, avfilter_get_by_name("abuffersink"),
                                             "ffplay_abuffersink");
    if (!filt_asink) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    // 设置abuffersink滤镜:设置采样格式,采样率,声道布局。采样格式固定为s16
    if ((ret = av_opt_set(filt_asink, "sample_formats", "s16", AV_OPT_SEARCH_CHILDREN)) < 0)
        goto end;

    if (force_output_format) {
        if ((ret = av_opt_set_array(filt_asink, "channel_layouts", AV_OPT_SEARCH_CHILDREN,
                                    0, 1, AV_OPT_TYPE_CHLAYOUT, &is->audio_tgt.ch_layout)) < 0)
            goto end;
        if ((ret = av_opt_set_array(filt_asink, "samplerates", AV_OPT_SEARCH_CHILDREN,
                                    0, 1, AV_OPT_TYPE_INT, &is->audio_tgt.freq)) < 0)
            goto end;
    }

    // 初始化abuffersink滤镜,此函数调用完毕后abuffersink滤镜初始化完毕
    ret = avfilter_init_dict(filt_asink, NULL);
    if (ret < 0)
        goto end;

    // 配置并建立滤镜图,此滤镜图可供后续滤镜操作中直接使用
    if ((ret = configure_filtergraph(is->agraph, afilters, filt_asrc, filt_asink)) < 0)
        goto end;

    is->in_audio_filter  = filt_asrc;
    is->out_audio_filter = filt_asink;

end:
    if (ret < 0)
        avfilter_graph_free(&is->agraph);
    av_bprint_finalize(&bp, NULL);

    return ret;
}

和视频滤镜图的配置过程类似,音频滤镜图配置过程如下:

  1. 创建和初始化 abuffer 滤镜 (滤镜图输入端点)
  2. 创建和初始化 abuffersink 滤镜 (滤镜图输出端点)
  3. 配置生成滤镜图,将所有滤镜 (abuffer 滤镜,abuffersink 滤镜,afilters 参数中包含的其他滤镜) 连接在一起

ffplay 播放音频固定设置为 s16 采样格式,所以 configure_audio_filters() 中将滤镜图输出端点的音频采样格式设置为 s16,后面打开 SDL 音频设备时也同样设置了音频采样格式为 s16。音频参数总共有三个:采样格式、采样率和声道布局,采样格式已设置为固定,所以在配置滤镜和配置 SDL 音频设备时,只关注采样率和声道布局两个参数。

configure_audio_filters() 会被调用两次,第一次调用是在解复用线程的 stream_component_open() 函数中,第三个参数为 0,第二次调用是在音频解码线程主函数 addio_thread() 中,第三个参数为 1。第一次调用是构建了一个临时的滤镜图,让 FFmpeg 滤镜图和 SDL 音频设备去协商一套音频参数,所以第一次配置的临时滤镜图没有设置滤镜图输出音频参数 (force_output_format 参数为 0);第二次调用则构建了一个真正要使用的滤镜图,将 SDL 音频设备使用的音频参数 (保存在 is->audio_tgt 中) 配置为滤镜图的输出音频参数 (force_output_format 参数为 1)。

这两次配置过程详情参考接下来的两小节。

6.4.1.2 第一次配置滤镜

第一次配置音频滤镜图,是在解复用线程 (read_thread) 里的 stream_component_open() 中打开音频设备之前,如下:

static int stream_component_open(VideoState *is, int stream_index)
{
    ...
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        {
            AVFilterContext *sink;

            // 首次配置音频滤镜图,获取滤镜协商的音频格式,供下一步audio_open()使用
            is->audio_filter_src.freq           = avctx->sample_rate;
            ret = av_channel_layout_copy(&is->audio_filter_src.ch_layout, &avctx->ch_layout);
            if (ret < 0)
                goto fail;
            is->audio_filter_src.fmt            = avctx->sample_fmt;
            if ((ret = configure_audio_filters(is, afilters, 0)) < 0)
                goto fail;
            sink = is->out_audio_filter;
            sample_rate    = av_buffersink_get_sample_rate(sink);
            ret = av_buffersink_get_ch_layout(sink, &ch_layout);
            if (ret < 0)
                goto fail;
        }

        /* prepare audio output */
        // 使用上一步滤镜图的音频参数作为期望参数打开音频设备,
        // 打开音频设备后设备实际使用的音频参数会存入输出参数is->audio_tgt中
        // 期望的音频参数ch_layout和sample_rate会被存在is->audio_filter_src
        // 期望参数和实际参数可能不同
        // is->audio_tgt是SDL最终播放允许的实际参数
        // is->audio_src是解码生成的音频frame中的参数
        // 例如:SDL2.0不支持planar格式音频,如果frame中音频格式是planar,则将frame音频数据直接送到
        //       音频缓冲区是无法正常播放的,此时需要进行音频重采样:
        //       将is->audio_src格式转换为is->audio_tgt格式
        if ((ret = audio_open(is, &ch_layout, sample_rate, &is->audio_tgt)) < 0)
            goto fail;
        is->audio_hw_buf_size = ret;                // SDL音频缓冲区大小
        is->audio_src = is->audio_tgt;              // 此处暂赋值为audio_tgt,后面重采样函数中会做判断
        ...
        // 创建音频解码线程
        if ((ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is)) < 0)
            goto out;
        SDL_PauseAudioDevice(audio_dev, 0);     // 启用音频回调,开始播放音频
        break;
        ...
    }
    ...
}

第一次配置音频滤镜图的目的,是构建一个临时的音频滤镜图,让这个滤镜图和 SDL 音频设备协商音频参数。构建临时滤镜图后,滤镜图内部会协商出输出端点的音频参数,用这个音频参数作为期望的参数打开 SDL 音频设备,SDL 会参考这组参数来打开音频设备,但打开的音频设备实际使用的音频参数是有可能和期望参数不同的。打开音频设备时,期望的音频参数由变量 sample_rate 和 ch_layout 表示,打开音频设备后设备使用的实际参数则保存在 is->audio_tgt 中,并进一步将其赋值给 is->audio_src。

在第一次配置音频滤镜图时,configure_audio_filters() 第三个参数 force_output_format 为 0,不强制指定滤镜图的输出音频参数,而使用滤镜图自己协商出的参数去和 SDL 音频设备进一步协商,得到 SDL 音频设备的实际参数,再反过来在第二次配置滤镜图时,把 SDL 音频设备的实际参数设置为滤镜图的目标音频参数。

另外注意,此时音频解码线程还未启动,无法获取音频流中的参数,所以滤镜图源参数 is->audio_filter_src 使用的是从容器中获取到的音频参数。

6.4.1.3 第二次配置滤镜

第二次配置音频滤镜图,是在音频解码线程主函数 audio_thread() 中,相关代码如下:

static int audio_thread(void *arg)
{
    ...
    do {
        if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0)
            goto the_end;

        if (got_frame) {
                tb = (AVRational){1, frame->sample_rate};

                // last_serial初始化为-1,所以reconfigure至少有一次为true,
                // 另外每次seek操作serial会变化reconfigure也会会true,
                reconfigure =
                    cmp_audio_fmts(is->audio_filter_src.fmt, is->audio_filter_src.ch_layout.nb_channels,
                                   frame->format, frame->ch_layout.nb_channels)    ||
                    av_channel_layout_compare(&is->audio_filter_src.ch_layout, &frame->ch_layout) ||
                    is->audio_filter_src.freq           != frame->sample_rate ||
                    is->auddec.pkt_serial               != last_serial;

                if (reconfigure) {
                    // 使用frame中的参数更新is->audio_filter_src并重配音频滤镜图
                    char buf1[1024], buf2[1024];
                    av_channel_layout_describe(&is->audio_filter_src.ch_layout, buf1, sizeof(buf1));
                    av_channel_layout_describe(&frame->ch_layout, buf2, sizeof(buf2));
                    av_log(NULL, AV_LOG_DEBUG,
                           "Audio frame changed from rate:%d ch:%d fmt:%s layout:%s serial:%d to rate:%d ch:%d fmt:%s layout:%s serial:%d\n",
                           is->audio_filter_src.freq, is->audio_filter_src.ch_layout.nb_channels, av_get_sample_fmt_name(is->audio_filter_src.fmt), buf1, last_serial,
                           frame->sample_rate, frame->ch_layout.nb_channels, av_get_sample_fmt_name(frame->format), buf2, is->auddec.pkt_serial);

                    is->audio_filter_src.fmt            = frame->format;
                    ret = av_channel_layout_copy(&is->audio_filter_src.ch_layout, &frame->ch_layout);
                    if (ret < 0)
                        goto the_end;
                    is->audio_filter_src.freq           = frame->sample_rate;
                    last_serial                         = is->auddec.pkt_serial;

                    // 再次配置音频滤镜图,注意stream_component_open中第一次配置音频滤镜图时是使用的
                    // 容器中的参数赋值给is->audio_filter_src,此处使用解码frame中的参数重配滤镜图,
                    // 且第三个参数为1,将使用打开SDL音频设备时的实际参数(is->audio_tgt.ch_layout和
                    // is->audio_tgt.freq)配置滤镜图的输出音频格式
                    if ((ret = configure_audio_filters(is, afilters, 1)) < 0)
                        goto the_end;
                }

            ...
        }
    } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);
    ...
}

第二次配置滤镜图,会在 configure_audio_filters() 内部先销毁前面打开音频设备时所配置的临时滤镜图然后重新配置生成一个新的滤镜图。第二次配置的滤镜图将会被真正使用,通过将 configure_audio_filters() 第三个参数设置为 1,来将音频设备的音频参数设置为滤镜图的输出音频参数 (也即滤镜转换的目标参数)。滤镜图的源参数则是解码音频帧中的参数。

注意,第一次配置滤镜图时,滤镜图是源参数是从容器中获取的,因为那时只能拿到容器中的参数,解码线程还未启动,拿不到音频帧中的参数。此处,解码线程中拿到了音频帧中的参数,此参数才是音频流的实际参数。容器中有音频参数和音频编码流中的实际音频参数是有可能不同的,以编码流中的实际参数为准。

我们看 reconfigure 变量的赋值语句,reconfigure 至少有一次值为 true,所以第二次配置滤镜肯定会被执行至少一次。

6.4.2 滤镜的使用

滤镜的使用就比较简单了,将解码后的音频帧送经滤镜处理,然后存入音频 frame 队列,如下:

static int audio_thread(void *arg)
{
    ...
    do {
        // 音频解码
        if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0)
            goto the_end;

        if (got_frame) {
            ...

            // 音频经滤镜处理:音频帧送入滤镜
            if ((ret = av_buffersrc_add_frame(is->in_audio_filter, frame)) < 0)
                goto the_end;

            // 音频经滤镜处理:从滤镜中取出已处理的音频帧
            while ((ret = av_buffersink_get_frame_flags(is->out_audio_filter, frame, 0)) >= 0) {
                ...
                // 将frame数据拷入af->frame,af->frame指向音频frame队列尾部
                av_frame_move_ref(af->frame, frame);
                // 更新音频frame队列大小及写指针
                frame_queue_push(&is->sampq);
                ...
            }
            ...
        }
    } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);
    ...
}

音频滤镜底层实际也是调用的重采样函数 swr_convert() 来实际音频重采样功能。

6.5 播放线程中的音频重采样

音频重采样在 audio_decode_frame() 中实现,它从音频 frame 队列取出一个 frame,经重采样后输出。audio_decode_frame() 函数名起得不好,此函数和解码无关。audio_decode_frame() 主要调用了 synchronize_audio() 和 swr_convert() 两个函数,synchronize_audio() 用于音视频同步,swr_convert() 用于重采样, 重采样的细节很琐碎,细节参考注释:

static int audio_decode_frame(VideoState *is)
{
    int data_size, resampled_data_size;
    av_unused double audio_clock0;
    int wanted_nb_samples;
    Frame *af;

    if (is->paused)
        return -1;

    do {
#if defined(_WIN32)
        while (frame_queue_nb_remaining(&is->sampq) == 0) {
            if ((av_gettime_relative() - audio_callback_time) > 1000000LL * is->audio_hw_buf_size / is->audio_tgt.bytes_per_sec / 2)
                return -1;
            av_usleep (1000);
        }
#endif
        // 若队列头部可读,则由af指向可读帧
        if (!(af = frame_queue_peek_readable(&is->sampq)))
            return -1;
        frame_queue_next(&is->sampq);
    } while (af->serial != is->audioq.serial);

    // 根据frame中指定的音频参数获取缓冲区的大小
    data_size = av_samples_get_buffer_size(NULL, af->frame->ch_layout.nb_channels,  // 本行两参数:linesize,声道数
                                           af->frame->nb_samples,                   // 本行一参数:本帧中包含的单个声道中的样本数
                                           af->frame->format, 1);                   // 本行两参数:采样格式,不对齐

    // 获取样本数校正值:若同步时钟是音频(即视频同步到音频),则不调整样本数;否则根据同步需要调整样本数
    wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);

    // |-------------------音频解码线程-----------------------------|--------------音频播放线程--------------------|
    // 音频packet==>[解码]==>音频frame==>[滤镜(包含重采样)]==>音频frame(队列)==>[音频重采样]==>音频frame==>[SDL播放]
    // 音频解码线程中重采样格式转换为:is->audio_filter_src ==> is->audio_tgt
    // 音频播放线程中重采样格式转换为:is->audio_src ==> is->audio_tgt
    // is->audio_tgt是SDL播放音频时实际使用的音频参数,是在audio_open()中取得的参数
    // stream_component_open()调用了audio_open()后马上执行了“is->audio_src = is->audio_tgt”
    // 解码线程中滤镜进行重采样处理后的帧的实际格式在af->frame中,这个格式实际就是is->audio_tgt,也是is->audio_src
    // 看此处的if语句,前三句不等式不成立,因为af->frame的格式就等于is->audio_src的格式,只有第四个条件可能成立,
    // 第四个条件在同步时钟不是音频时钟是成立,此时需要对音频帧的大小(样本数)进行调整,就会执行重采样
    if (af->frame->format        != is->audio_src.fmt            ||
        av_channel_layout_compare(&af->frame->ch_layout, &is->audio_src.ch_layout) ||
        af->frame->sample_rate   != is->audio_src.freq           ||
        (wanted_nb_samples       != af->frame->nb_samples && !is->swr_ctx)) {
        int ret;
        swr_free(&is->swr_ctx);
        // 使用frame(源)和is->audio_tgt(目标)中的音频参数来设置is->swr_ctx
        ret = swr_alloc_set_opts2(&is->swr_ctx,
                            &is->audio_tgt.ch_layout, is->audio_tgt.fmt, is->audio_tgt.freq,
                            &af->frame->ch_layout, af->frame->format, af->frame->sample_rate,
                            0, NULL);
        if (ret < 0 || swr_init(is->swr_ctx) < 0) {
            av_log(NULL, AV_LOG_ERROR,
                   "Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n",
                    af->frame->sample_rate, av_get_sample_fmt_name(af->frame->format), af->frame->ch_layout.nb_channels,
                    is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.ch_layout.nb_channels);
            swr_free(&is->swr_ctx);
            return -1;
        }
        // 使用frame中的参数更新is->audio_src,第一次更新后后面基本不用执行此if分支了,因为一个音频流中各frame通用参数一样
        if (av_channel_layout_copy(&is->audio_src.ch_layout, &af->frame->ch_layout) < 0)
            return -1;
        is->audio_src.freq = af->frame->sample_rate;
        is->audio_src.fmt = af->frame->format;
    }

    if (is->swr_ctx) {
        // 重采样输入参数:输入音频缓冲区
        const uint8_t **in = (const uint8_t **)af->frame->extended_data;
        // 重采样输出参数:输出音频缓冲区
        uint8_t **out = &is->audio_buf1;
        // 重采样输出参数:输出音频样本数,根据采样率转换(多加了256),是swr_convert()的第三个参数,
        //                 此参数应尽量设置大一点,如果设置的比实际需要的值小,输入数据会被缓存
        //                 此处简单粗暴处理,在计算出的小数取整的基础上又直接多加了256
        int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate + 256;
        // 输出音频缓冲区尺寸(以字节为单位)
        int out_size  = av_samples_get_buffer_size(NULL, is->audio_tgt.ch_layout.nb_channels, out_count, is->audio_tgt.fmt, 0);
        int len2;
        if (out_size < 0) {
            av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failed\n");
            return -1;
        }
        // 如果frame中的样本数经过校正,则条件成立
        if (wanted_nb_samples != af->frame->nb_samples) {
            // 重采样补偿:不清楚参数怎么算的
            if (swr_set_compensation(is->swr_ctx, (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq / af->frame->sample_rate,
                                        wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate) < 0) {
                av_log(NULL, AV_LOG_ERROR, "swr_set_compensation() failed\n");
                return -1;
            }
        }
        // 调整重彩样输出缓冲区,缓冲区尺寸保存在is->audio_buf1_size中
        av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size);
        if (!is->audio_buf1)
            return AVERROR(ENOMEM);
        // 重采样函数有两个输入参数(输入缓冲区和输入样本数)和两个输出参数(输出缓冲区和输出样本数)
        // 音频重采样:返回值是重采样后得到的一帧音频数据中单个声道的样本数
        len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);
        if (len2 < 0) {
            av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n");
            return -1;
        }
        if (len2 == out_count) {
            av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too small\n");
            if (swr_init(is->swr_ctx) < 0)
                swr_free(&is->swr_ctx);
        }
        is->audio_buf = is->audio_buf1;
        // 重采样返回的一帧音频数据大小(以字节为单位)
        resampled_data_size = len2 * is->audio_tgt.ch_layout.nb_channels * av_get_bytes_per_sample(is->audio_tgt.fmt);
    } else {
        // 未经重采样,则将指针指向frame中的音频数据
        is->audio_buf = af->frame->data[0];
        resampled_data_size = data_size;
    }

    audio_clock0 = is->audio_clock;
    /* update the audio clock with the pts */
    if (!isnan(af->pts))
        is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;
    else
        is->audio_clock = NAN;
    is->audio_clock_serial = af->serial;
#ifdef DEBUG
    {
        static double last_clock;
        printf("audio: delay=%0.3f clock=%0.3f clock0=%0.3f\n",
               is->audio_clock - last_clock,
               is->audio_clock, audio_clock0);
        last_clock = is->audio_clock;
    }
#endif
    return resampled_data_size;
}

前面“6.2 节 音频解码和播放过程概览”提到过,音视频同步方案比较常用的是视频同步到音频,这种情况下播放线程中的重采样过程是不会执行的,这里的关键就是 audio_decode_frame() 中的如下语句:

if (af->frame->format        != is->audio_src.fmt            ||
    av_channel_layout_compare(&af->frame->ch_layout, &is->audio_src.ch_layout) ||
    af->frame->sample_rate   != is->audio_src.freq           ||
    (wanted_nb_samples       != af->frame->nb_samples && !is->swr_ctx)) {

因为音频解码线程中已经使用滤镜做过一次重采样,那么滤镜处理后的音频帧 af->frame 里的格式就是 is->audio_tgt,而 stream_component_open() 函数里调用了 audio_open() 后马上执行了 is->audio_src = is->audio_tgt,那么 af->frame、is->audio_tgt 和 is->audio_src 这三个音频参数其实是相等的,所以此处 if 语句中前 3 个不等式不成立。只有第 4 个不等式可能成立。当同步方案是音频同步到视频或外部时钟时,需要根据同步时钟调整音频帧的样本数,此时 wanted_nb_samples 和 af->frame->nb_samples 不相等,if 条件成立,就会执行重采样过程。

posted @ 2019-01-24 09:34  叶余  阅读(4246)  评论(0)    收藏  举报