音视频流媒体开发【二十六】ffplay播放器-音频输出和音频重采样

音视频流媒体开发-目录

7 ⾳频输出模块

ffplay的⾳频输出通过SDL实现。

⾳频输出的主要流程:

  • 打开SDL⾳频设备,设置参数
  • 启动SDL⾳频设备播放
  • SDL⾳频回调函数读取数据,这个时候我们就要从FrameQueue读取frame填充回调函数提供的buffer空间。

audio的输出在SDL下是被动的,即在开启SDL⾳频后,当SDL需要数据输出时则通过回调函数的⽅式告诉应⽤者需要传⼊多少数据,但这⾥存在⼀些问题:

  • ffmpeg解码⼀个AVPacket的⾳频到AVFrame后,在AVFrame中存储的⾳频数据⼤⼩与SDL回调所需要的数据不⼀定相等 (回调函数每次要获取的数据量都是固定);
  • 特别是如果要实现声⾳变速播放功能,那每帧AVFrame做变速后的数据⼤⼩⼤概率和SDL回调锁需要的数据⼤⼩不⼀致。

这就需要再增加⼀级缓冲区解决问题,即是从FrameQueue队列读取到Frame的数据后,先缓存到⼀个buffer⾥,然后再从该buffer读取数据给到SDL回调函数。

在audio输出时,主要模型如下图:

在这个模型中,sdl通过sdl_audio_callback函数向ffplay要⾳频数据,ffplay将sampq中的数据通过audio_decode_frame 函数取出,放⼊ is->audio_buf ,然后送出给sdl。在后续回调时先找audio_buf 要数据,数据不⾜的情况下,再调⽤ audio_decode_frame 补充 audio_buf

注意:audio_decode_frame 这个函数名很具有迷惑性,实际上,这个函数是没有解码功能的!这个函数主要是处理sampq到audio_buf的过程,最多只是执⾏了重采样(数据源和输出参数不⼀致时则做重采样)。

打开SDL⾳频设备

SDL⾳频输出的参数是⼀开始就设置好的,当码流的解出来的⾳频参数和预设的输出参数不⼀致时,则需要重采样成

预设参数⼀致数据,这样才能正常播放。

⾳频设备的打开实际是在解复⽤线程中实现的。解复⽤线程中先打开⾳频设备(设定⾳频回调函数供SDL⾳频播放线程回调),然后再创建⾳频解码线程。调⽤链如下:

main() -->
stream_open() -->
read_thread() -->
stream_component_open() -->
audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt);

先看打开sdl⾳频输出的代码(stream_component_open函数):

//代码在stream_component_open
//先不看filter相关代码,即默认CONFIG_AVFILTER宏为0
//从avctx(即AVCodecContext)中获取⾳频格式参数
sample_rate = avctx->sample_rate;
nb_channels = avctx->channels;
channel_layout = avctx->channel_layout;
//调⽤audio_open打开sdl⾳频输出,实际打开的设备参数保存在audio_tgt,返回值表示输出设备的缓冲区⼤⼩
if ((ret = audio_open(is, channel_layout, nb_channels, sample_rate,&is->audio_tgt)) < 0)
    goto fail;
is->audio_hw_buf_size = ret;
is->audio_src = is->audio_tgt; //暂且将数据源参数等同于⽬标输出参数
//初始化audio_buf相关参数
is->audio_buf_size = 0;
is->audio_buf_index = 0;

由于不同的⾳频输出设备⽀持的参数不同,⾳轨的参数不⼀定能被输出设备⽀持(此时就需要重采样了), audio_tgt 就保存了输出设备参数。

audio_open是ffplay封装的函数,会优先尝试请求参数能否打开输出设备,尝试失败后会⾃动查找最佳的参数重新尝试。不再具体分析。

audio_src ⼀开始与 audio_tgt 是⼀样的,如果输出设备⽀持⾳轨参数,那么 audio_src 可以⼀直保持与 audio_tgt ⼀致,否则将在后⾯代码中⾃动修正为⾳轨参数,并引⼊重采样机制。

最后初始化了⼏个audio_buf相关的参数。这⾥介绍下audio_buf相关的⼏个变量:

  • audio_buf: 从要输出的AVFrame中取出的⾳频数据(PCM),如果有必要,则对该数据重采样。
  • audio_buf_size: audio_buf的总⼤⼩
  • audio_buf_index: 下⼀次可读的audio_buf的index位置。
  • audio_write_buf_size:audio_buf剩余的buffer⻓度,即audio_buf_size - audio_buf_index

在 audio_open 函数内,通过通过 SDL_OpenAudioDevice 注册 sdl_audio_callback 函数为⾳频输出的回调函数。那么,主要的⾳频输出的逻辑就在 sdl_audio_callback 函数内了。

打开⾳频设备audio_open

audio_open()函数填⼊期望的⾳频参数,打开⾳频设备后,将实际的⾳频参数存⼊输出参数is->audio_tgt中,后⾯⾳频播放线程⽤会⽤到此参数,使⽤此参数将原始⾳频数据重采样,转换为⾳频设备⽀持的格式。

static int audio_open(void *opaque, int64_t wanted_channel_layout,int wanted_nb_channels, 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;

    env = SDL_getenv("SDL_AUDIO_CHANNELS");
    if (env) { // 若环境变量有设置,优先从环境变量取得声道数和声道布局
        wanted_nb_channels = atoi(env);
        wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels);
    }
    if (!wanted_channel_layout || wanted_nb_channels != av_get_channel_layout_nb_channels(wanted_channel_layout)) {
        wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels);
        wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;
    }
    // 根据channel_layout获取nb_channels,当传⼊参数wanted_nb_channels不匹配时,此处会作修正
    wanted_nb_channels = av_get_channel_layout_nb_channels(wanted_channel_layout);
    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 channelcount!\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的值
    // ⾳频采样格式有两⼤类型:planar和packed,假设⼀个双声道⾳频⽂件,⼀个左声道采样点记作L,⼀个右声道采样点记作R,则:
    // planar存储格式:(plane1)LLLLLLLL...LLLL (plane2)RRRRRRRR...RRRR
    // packed存储格式:(plane1)LRLRLRLR...........................LRLR
    // 在这两种采样类型下,⼜细分多种采样格式,如AV_SAMPLE_FMT_S16、AV_SAMPLE_FMT_S16P等,
    // 注意SDL2.0⽬前不⽀持planar格式
    // channel_layout是int64_t类型,表示⾳频声道布局,每bit代表⼀个特定的声道,参考channel_layout.h中的定义,⼀⽬了然
    // 数据量(bits/秒) = 采样率(Hz) * 采样深度(bit) * 声道数
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.silence = 0;
    /*
     * ⼀次读取多⻓的数据
     * SDL_AUDIO_MAX_CALLBACKS_PER_SEC⼀秒最多回调次数,避免频繁的回调
     * Audio buffer size in samples (power of 2)
     */
    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;
    wanted_spec.userdata = opaque;
    // 打开⾳频设备并创建⾳频处理线程。期望的参数是wanted_spec,实际得到的硬件参数是spec
    // 1) SDL提供两种使⾳频设备取得⾳频数据⽅法:
    // a. push,SDL以特定的频率调⽤回调函数,在回调函数中取得⾳频数据
    // b. pull,⽤户程序以特定的频率调⽤SDL_QueueAudio(),向⾳频设备提供数据。此种情况wanted_spec.callback=NULL
    // 2) ⾳频设备打开后播放静⾳,不启动回调,调⽤SDL_PauseAudio(0)后启动回调,开始正常播放⾳频
    // SDL_OpenAudioDevice()第⼀个参数为NULL时,等价于SDL_OpenAudio()
    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;
            }
        }
        wanted_channel_layout = av_get_default_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) {
        wanted_channel_layout = av_get_default_channel_layout(spec.channels);
        if (!wanted_channel_layout) {
            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保存的参数,就是在做重采样的时候要转成的格式。
    audio_hw_params->fmt = AV_SAMPLE_FMT_S16;
    audio_hw_params->freq = spec.freq;
    audio_hw_params->channel_layout = wanted_channel_layout;
    audio_hw_params->channels = spec.channels;
    /* audio_hw_params->frame_size这⾥只是计算⼀个采样点占⽤的字节数 */
    audio_hw_params->frame_size = av_samples_get_buffer_size(NULL,audio_hw_params->channels,
                                                             1, audio_hw_params->fmt, 1);
    audio_hw_params->bytes_per_sec = av_samples_get_buffer_size(NULL, audio_hw_params->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;
    }
    // ⽐如2帧数据,⼀帧就是1024个采样点, 1024*2*2 * 2 = 8096字节
    return spec.size; /* 硬件内部缓存的数据字节, samples * channels *byte_per_sample */
}

回调函数逻辑sdl_audio_callback

再来看 sdl_audio_callback

static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
    VideoState *is = opaque;
    int audio_size, len1;

    audio_callback_time = av_gettime_relative();

    while (len > 0) { // 循环读取,直到读取到⾜够的数据
        /* (1)如果is->audio_buf_index < is->audio_buf_size则说明上次拷⻉还剩余⼀些数据,
         * 先拷⻉到stream再调⽤audio_decode_frame
         * (2)如果audio_buf消耗完了,则调⽤audio_decode_frame重新填充audio_buf
         */
        if (is->audio_buf_index >= is->audio_buf_size) {
            audio_size = audio_decode_frame(is);
            if (audio_size < 0) {
                /* if error, just output silence */
                is->audio_buf = NULL;
                is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size
                * is->audio_tgt.frame_size;
            } else {
                if (is->show_mode != SHOW_MODE_VIDEO)
                    update_sample_display(is, (int16_t *)is->audio_buf, audio_size);
                is->audio_buf_size = audio_size;
            }
            is->audio_buf_index = 0;
        }
        //根据缓冲区剩余⼤⼩量⼒⽽⾏
        len1 = is->audio_buf_size - is->audio_buf_index;
        if (len1 > len)
            len1 = len;
        //根据audio_volume决定如何输出audio_buf
        /* 判断是否为静⾳,以及当前⾳量的⼤⼩,如果⾳量为最⼤则直接拷⻉数据 */
        if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)
            memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
        else {
            memset(stream, 0, len1);
            // 调整⾳量
            /* 如果处于mute状态则直接使⽤stream填0数据, 暂停时is->audio_buf= NULL */
            if (!is->muted && is->audio_buf)
                SDL_MixAudioFormat(stream, (uint8_t *)is->audio_buf+ is->audio_buf_index,
                                   AUDIO_S16SYS, len1, is->audio_volume);
        }
        len -= len1;
            stream += len1;
        /* 更新is->audio_buf_index,指向audio_buf中未被拷⻉到stream的数据(剩余数据)的起始位置 */
        is->audio_buf_index += len1;
    }
    is->audio_write_buf_size = is->audio_buf_size - is->audio_buf_index;
    /* Let's assume the audio driver that is used by SDL has two periods. */
    if (!isnan(is->audio_clock)) {
        set_clock_at(&is->audclk,
                     is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec,
                     is->audio_clock_serial,
                     audio_callback_time / 1000000.0);
        sync_clock_to_slave(&is->extclk, &is->audclk);
    }
}

sdl_audio_callback 函数是⼀个典型的缓冲区输出过程,看代码和注释应该可以理解。具体看3个细节:

  • 输出audio_buf到stream,如果audio_volume为最⼤⾳量,则只需memcpy复制给stream即可。否则,可以利⽤SDL_MixAudioFormat进⾏⾳量调整和混⾳
  • 如果audio_buf消耗完了,就调⽤ audio_decode_frame 重新填充audio_buf。接下来会继续分析audio_decode_frame函数
  • set_clock_at更新audclk时,audio_clock是当前audio_buf的显示结束时间(pts+duration),由于audio driver本身会持有⼀⼩块缓冲区,典型地会是两块交替使⽤,所以有 2 * is->audio_hw_buf_size,⾄于为什么还要 audio_write_buf_size,⼀图胜千⾔。

我们先来is->audio_clock是在audio_decode_frame赋值:

is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;

从这⾥可以看出来,这⾥的时间戳是audio_buf结束位置的时间戳,⽽不是audio_buf起始位置的时间戳,所以当audio_buf有剩余时(),那实际数据的pts就变成is->audio_clock - (double)(is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec,即是

再考虑到,实质上audio_hw_buf_size*2这些数据实际都没有播放出去,所以就有is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec。

再加上我们在SDL回调进⾏填充



时,实际上



是有开始被播放,所以我们这⾥采⽤的相对时间是,刚回调产⽣的,就是内部[图片上传失败...(image-5940bc-1679110797848)]
image.png

在播放的时候,那相对时间实际也在⾛。

最终
set_clock_at(&is->audclk, is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial,audio_callback_time / 1000000.0);

回调函数读取数据

接下来看下 audio_decode_frame (省略重采样代码):

static int audio_decode_frame(VideoState *is)
{
    int data_size, resampled_data_size;
    int64_t dec_channel_layout;
    av_unused double audio_clock0;
    int wanted_nb_samples;
    Frame *af;
    if (is->paused)//暂停状态,返回-1,sdl_audio_callback会处理为输出静⾳
        return -1;
    do {//1\. 从sampq取⼀帧,必要时丢帧
        if (!(af = frame_queue_peek_readable(&is->sampq)))
            return -1;
        frame_queue_next(&is->sampq);
    } while (af->serial != is->audioq.serial);
    //2\. 计算这⼀帧的字节数
    data_size = av_samples_get_buffer_size(NULL, af->frame->channels,
                                           af->frame->nb_samples,
                                           af->frame->format, 1);
    //[]计算dec_channel_layout,⽤于确认是否需要重新初始化重采样(难道af->channel_layout不可靠?不理解)
    dec_channel_layout =
    (af->frame->channel_layout && af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ?
    af->frame->channel_layout : av_get_default_channel_layout(af->frame->channels);
    wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);
    //[]判断是否需要重新初始化重采样
    if (af->frame->format != is->audio_src.fmt ||
        dec_channel_layout != is->audio_src.channel_layout ||
        af->frame->sample_rate != is->audio_src.freq ||
        (wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx)) {
        //……
    }
    //3\. 获取这⼀帧的数据
    if (is->swr_ctx) {//[]如果初始化了重采样,则对这⼀帧数据重采样输出
    }else {
        is->audio_buf = af->frame->data[0];
        resampled_data_size = data_size;
    }
    audio_clock0 = is->audio_clock;//audio_clock0⽤于打印调试信息
    //4\. 更新audio_clock,audio_clock_serial
    /* 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;
    return resampled_data_size;//返回audio_buf的数据⼤⼩
}

audio_decode_frame 并没有真正意义上的 decode 代码,最多是进⾏了重采样。主流程有以下步骤:

  1. 从sampq取⼀帧,必要时丢帧。如发⽣了seek,此时serial会不连续,就需要丢帧处理
  2. 计算这⼀帧的字节数。通过av_samples_get_buffer_size可以⽅便计算出结果
  3. 获取这⼀帧的数据。对于frame格式和输出设备不同的,需要重采样;如果格式相同,则直接拷⻉指针输出即可。总之,需要在audio_buf中保存与输出设备格式相同的⾳频数据
  4. 更新audio_clock,audio_clock_serial。⽤于设置audclk.

在省略了重采样代码后看,相对容易理解。

⾄此,⾳频输出的主要代码就分析完了。中间我们省略了filter和resample相关的代码,有研究后再补充。

8 ⾳频重采样

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

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

  1. 打开⾳频设备时进⾏的准备⼯作:确定SDL⽀持的⾳频格式,作为后期⾳频重采样的⽬标格式。这⼀部分内容参考《7 ⾳频输出模块》
  2. ⾳频播放线程中,取出⾳频帧后,若有需要(⾳频帧格式与SDL⽀持⾳频格式不匹配)则进⾏重采样,否则直接输出

重采样逻辑

⾳频重采样在 audio_decode_frame() 中实现, audio_decode_frame() 就是从⾳频frame队列中取出⼀个frame,按指定格式经过重采样后输出(解码不是在该函数进⾏)。

重采样的细节很琐碎,直接看注释:

static int audio_decode_frame(VideoState *is)
{
    int data_size, resampled_data_size;
    int64_t dec_channel_layout;
    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->channels,
                                           af->frame->nb_samples,
                                           af->frame->format, 1);
    // 获取声道布局
    dec_channel_layout = (af->frame->channel_layout &&
                          af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ?
                          af->frame->channel_layout : av_get_default_channel_layout(af->frame->channels);
    // 获取样本数校正值:若同步时钟是⾳频,则不调整样本数;否则根据同步需要调整样本数
    wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);
    // is->audio_tgt是SDL可接受的⾳频帧数,是audio_open()中取得的参数
    // 在audio_open()函数中⼜有"is->audio_src = is->audio_tgt""
    // 此处表示:如果frame中的⾳频参数 == is->audio_src == is->audio_tgt,
    // 那⾳频重采样的过程就免了(因此时is->swr_ctr是NULL)
    // 否则使⽤frame(源)和is->audio_tgt(⽬标)中的⾳频参数来设置is->swr_ctx,
    // 并使⽤frame中的⾳频参数来赋值is->audio_src
    if (af->frame->format != is->audio_src.fmt ||
        dec_channel_layout != is->audio_src.channel_layout ||
        af->frame->sample_rate != is->audio_src.freq||
        (wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx)) {
        swr_free(&is->swr_ctx);
        is->swr_ctx = swr_alloc_set_opts(NULL,
                                         is->audio_tgt.channel_layout, // ⽬标输出
                                         is->audio_tgt.fmt,
                                         is->audio_tgt.freq,
                                         dec_channel_layout,// 数据源
                                         af->frame->format,
                                         af->frame->sample_rate,0, NULL);
        if (!is->swr_ctx || 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->channels,is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels);
            swr_free(&is->swr_ctx);
            return -1;
        }
        is->audio_src.channel_layout = dec_channel_layout;
        is->audio_src.channels = af->frame->channels;
        is->audio_src.freq = af->frame->sample_rate;
        is->audio_src.fmt = af->frame->format;
    }

    if (is->swr_ctx) {
        // 重采样输⼊参数1:输⼊⾳频样本数是af->frame->nb_samples
        const uint8_t **in = (const uint8_t **)af->frame->extended_data;
        // 重采样输⼊参数2:输⼊⾳频缓冲区
        uint8_t **out = &is->audio_buf1;
        // 重采样输出参数1:输出⾳频缓冲区尺⼨
        int out_count = (int64_t)wanted_nb_samples *
        is->audio_tgt.freq / af->frame->sample_rate + 256;
        // 重采样输出参数2:输出⾳频缓冲区
        int out_size = av_samples_get_buffer_size(NULL, is->audio_tgt.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;
            }
        }
        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 probablytoo 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.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;
}

样本补偿

swr_set_compensation说明

/**
* Activate resampling compensation ("soft" compensation). This function is
* internally called when needed in swr_next_pts().
*
* @param[in,out] s allocated Swr context. If it is not initialized,
* or SWR_FLAG_RESAMPLE is not set, swr_init() is
* called with the flag set.
* @param[in] sample_delta delta in PTS per sample(每个样本的增量,单位pts)
* @param[in] compensation_distance number of samples to compensate for(要补偿的样本数)
* @return >= 0 on success, AVERROR error codes if:
* @li @c s is NULL,
* @li @c compensation_distance is less than 0,
* @li @c compensation_distance is 0 but sample_delta is not,
* @li compensation unsupported by resampler, or
* @li swr_init() fails when called.
*/

int swr_set_compensation(struct SwrContext *s, int sample_delta, int compensation_distance);

sample_delta: (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq / af->frame->sample_rate;
compensation_distance: wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate)

注意是谁是除数,谁是被除数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容