ffplay.c学习-4-⾳频输出和⾳频重采样


目录

  1. ⾳频输出模块

    1. 打开SDL⾳频设备
    2. 打开⾳频设备audio_open
    3. 回调函数逻辑sdl_audio_callback
    4. 回调函数读取数据
  2. ⾳频重采样
    1. 重采样逻辑
    2. 样本补偿

1. ⾳频输出模块

  1. ffplay的⾳频输出通过SDL实现。
  2. ⾳频输出的主要流程:
    1. 打开SDL⾳频设备,设置参数
    2. 启动SDL⾳频设备播放
    3. SDL⾳频回调函数读取数据,这个时候我们就要从FrameQueue读取frame填充回调函数提供的buffer空间。
  3. audio的输出在SDL下是被动的,即在开启SDL⾳频后,当SDL需要数据输出时则通过回调函数的⽅式告诉应⽤者需要传⼊多少数据,但这⾥存在⼀些问题:
    1. ffmpeg解码⼀个AVPacket的⾳频到AVFrame后,在AVFrame中存储的⾳频数据⼤⼩与SDL回调所需要的数据不⼀定相等 (回调函数每次要获取的数据量都是固定);
    2. 特别是如果要实现声⾳变速播放功能,那每帧AVFrame做变速后的数据⼤⼩⼤概率和SDL回调锁需要的数据⼤⼩不⼀致。
    3. 这就需要再增加⼀级缓冲区解决问题,即是从FrameQueue队列读取到Frame的数据后,先缓存到⼀个buffer⾥,然后再从该buffer读取数据给到SDL回调函数。
  4. 在audio输出时,主要模型如下图:
  5. 在这个模型中,sdl通过sdl_audio_callback函数向ffplay要⾳频数据,ffplay将sampq中的数据通过audio_decode_frame 函数取出,放⼊ is->audio_buf ,然后送出给sdl。在后续回调时先找audio_buf 要数据,数据不⾜的情况下,再调⽤ audio_decode_frame 补充 audio_buf
  6. 注意 audio_decode_frame 这个函数名很具有迷惑性,实际上,这个函数是没有解码功能的。这个函数主要是处理sampq到audio_buf的过程,最多只是执⾏了重采样(数据源和输出参数不⼀致时则做重采样)。

1. 打开SDL⾳频设备

  1. SDL⾳频输出的参数是⼀开始就设置好的,当码流的解出来的⾳频参数和预设的输出参数不⼀致时,则需要重采样成预设参数⼀致数据,这样才能正常播放。
  2. ⾳频设备的打开实际是在解复⽤线程中实现的。解复⽤线程中先打开⾳频设备(设定⾳频回调函数供SDL⾳频播放线程回调),然后再创建⾳频解码线程。调⽤链如下:
main() -->
stream_open() -->
read_thread() -->
stream_component_open() -->audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt);
  1. 先看打开sdl⾳频输出的代码(stream_component_open函数):
#else//从avctx(即AVCodecContext)中获取音频格式参数sample_rate    = avctx->sample_rate;nb_channels    = avctx->channels;channel_layout = avctx->channel_layout;
#endif/* prepare audio output 准备音频输出*///调用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;
  1. 由于不同的⾳频输出设备⽀持的参数不同,⾳轨的参数不⼀定能被输出设备⽀持(此时就需要重采样了), audio_tgt 就保存了输出设备参数。
  2. audio_open是ffplay封装的函数,会优先尝试请求参数能否打开输出设备,尝试失败后会⾃动查找最佳的参数重新尝试。不再具体分析。
  3. audio_src ⼀开始与 audio_tgt 是⼀样的,如果输出设备⽀持⾳轨参数,那么 audio_src 可以⼀直保持与 audio_tgt ⼀致,否则将在后⾯代码中⾃动修正为⾳轨参数,并引⼊重采样机制。
  4. 最后初始化了⼏个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
  5. 在 audio_open 函数内,通过通过 SDL_OpenAudioDevice 注册 sdl_audio_callback 函数为⾳频输出的回调函数。那么,主要的⾳频输出的逻辑就在 sdl_audio_callback 函数内了。

2. 打开⾳频设备audio_open

  1. 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 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的值// 音频采样格式有两大类型: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 = 8192字节return spec.size;    /* SDL内部缓存的数据字节, samples * channels *byte_per_sample */
}

3. 回调函数逻辑sdl_audio_callback

  1. 再来看 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) { //说明buf已经读满了,需要重新获取数据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; // 拷贝完,重新开始拷贝,重置为0}//根据缓冲区剩余大小量力而行len1 = is->audio_buf_size - is->audio_buf_index; // len1表示缓冲区剩余大小if (len1 > len)  // len = 3000 < len1 4096len1 = 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);// 3.调整音量/* 如果处于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);}
}
  1. sdl_audio_callback 函数是⼀个典型的缓冲区输出过程,看代码和注释应该可以理解。具体看3个细节:

    1. 输出audio_buf到stream,如果audio_volume为最⼤⾳量,则只需memcpy复制给stream即可。否则,可以利⽤SDL_MixAudioFormat进⾏⾳量调整和混⾳
    2. 如果audio_buf消耗完了,就调⽤ audio_decode_frame 重新填充audio_buf。接下来会继续分析audio_decode_frame函数
    3. set_clock_at更新audclk时,audio_clock是当前audio_buf的显示结束时间(pts+duration),由于audio driver本身会持有⼀⼩块缓冲区,典型地会是两块交替使⽤,所以有 2 * is->audio_hw_buf_size,⾄于为什么还要 audio_write_buf_size,⼀图胜千⾔。
  2. 我们先来is->audio_clock是在audio_decode_frame赋值:is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;
  3. 从这⾥可以看出来,这⾥的时间戳是audio_buf结束位置的时间戳,⽽不是audio_buf起始位置的时间戳,所以当audio_buf有剩余时,那实际数据的pts就变成is->audio_clock - (double)(is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec,即是
  4. 再考虑到,实质上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。
  5. 再加上我们在SDL回调进⾏填充 时,实际上 是有开始被播放,所以我们这⾥采⽤的相对时间是,刚回调产⽣的,就是内部 在播放的时候,那相对时间实际也在⾛.
  6. 最终
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);

4. 回调函数读取数据

  1. 接下来看下 audio_decode_frame :
/*** Decode one audio frame and return its uncompressed size.** The processed audio frame is decoded, converted if required, and* stored in is->audio_buf, with size in bytes given by the return* value.*/
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中指定的音频参数获取缓冲区的大小 af->frame->channels * af->frame->nb_samples * 2data_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_srcif (af->frame->format           != is->audio_src.fmt            || // 采样格式dec_channel_layout      != is->audio_src.channel_layout || // 通道布局af->frame->sample_rate  != is->audio_src.freq           || // 采样率// 第4个条件, 要改变样本数量, 那就是需要初始化重采样(wanted_nb_samples      != af->frame->nb_samples && !is->swr_ctx) // samples不同且swr_ctx没有初始化) {....}if (is->swr_ctx) {// 重采样输入参数1:输入音频样本数是af->frame->nb_samples// 重采样输入参数2:输入音频缓冲区const uint8_t **in = (const uint8_t **)af->frame->extended_data; // data[0] data[1]// 重采样输出参数1:输出音频缓冲区尺寸uint8_t **out = &is->audio_buf1; //真正分配缓存audio_buf1,指向是用audio_buf// 重采样输出参数2:输出音频缓冲区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.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) {int sample_delta = (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq/ af->frame->sample_rate;int compensation_distance = wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate;// swr_set_compensationif (swr_set_compensation(is->swr_ctx,sample_delta,compensation_distance) < 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 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.channels * av_get_bytes_per_sample(is->audio_tgt.fmt);} else {// 未经重采样,则将指针指向frame中的音频数据is->audio_buf = af->frame->data[0]; // s16交错模式data[0], fltp data[0] data[1]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;elseis->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;}
#endifreturn resampled_data_size;
}
  1. 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.

2. ⾳频重采样

  1. FFmpeg解码得到的⾳频帧的格式未必能被SDL⽀持,在这种情况下,需要进⾏⾳频重采样,即将⾳频帧格式转换为SDL⽀持的⾳频格式,否则是⽆法正常播放的。
  2. ⾳频重采样涉及两个步骤:
    1. 打开⾳频设备时进⾏的准备⼯作:确定SDL⽀持的⾳频格式,作为后期⾳频重采样的⽬标格式。这⼀部分内容参考⾳频输出模块
    2. ⾳频播放线程中,取出⾳频帧后,若有需要(⾳频帧格式与SDL⽀持⾳频格式不匹配)则进⾏重采样,否则直接输出

1. 重采样逻辑

  1. ⾳频重采样在 audio_decode_frame() 中实现, audio_decode_frame() 就是从⾳频frame队列中取出⼀个frame,按指定格式经过重采样后输出(解码不是在该函数进⾏)。
  2. 重采样的细节很琐碎,直接看注释:
/*** Decode one audio frame and return its uncompressed size.** The processed audio frame is decoded, converted if required, and* stored in is->audio_buf, with size in bytes given by the return* value.*/
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中指定的音频参数获取缓冲区的大小 af->frame->channels * af->frame->nb_samples * 2data_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_srcif (af->frame->format           != is->audio_src.fmt            || // 采样格式dec_channel_layout      != is->audio_src.channel_layout || // 通道布局af->frame->sample_rate  != is->audio_src.freq           || // 采样率// 第4个条件, 要改变样本数量, 那就是需要初始化重采样(wanted_nb_samples      != af->frame->nb_samples && !is->swr_ctx) // samples不同且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// 重采样输入参数2:输入音频缓冲区const uint8_t **in = (const uint8_t **)af->frame->extended_data; // data[0] data[1]// 重采样输出参数1:输出音频缓冲区尺寸uint8_t **out = &is->audio_buf1; //真正分配缓存audio_buf1,指向是用audio_buf// 重采样输出参数2:输出音频缓冲区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.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) {int sample_delta = (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq/ af->frame->sample_rate;int compensation_distance = wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate;// swr_set_compensationif (swr_set_compensation(is->swr_ctx,sample_delta,compensation_distance) < 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 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.channels * av_get_bytes_per_sample(is->audio_tgt.fmt);} else {// 未经重采样,则将指针指向frame中的音频数据is->audio_buf = af->frame->data[0]; // s16交错模式data[0], fltp data[0] data[1]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;elseis->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;}
#endifreturn resampled_data_size;
}

2. 样本补偿

  1. 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* @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);

ffplay.c学习-4-⾳频输出和⾳频重采样相关推荐

  1. ffplay.c学习-5-视频输出和尺⼨变换

    ffplay.c学习-5-视频输出和尺⼨变换 目录 视频输出模块 视频输出初始化 视频输出初始化主要流程 初始化窗⼝显示⼤⼩ 视频输出逻辑 video_refresh 计算上⼀帧应显示的时⻓,判断是否 ...

  2. FPGA学习日志——分频与降频divider

    文章目录 分频与降频 偶分频--六分频器divider_six 实验原理 实验代码 奇分频--五分频器divider_fie 实验原理 解决方案 实验代码 降频 实验原理 降频后的波形图 使用降频的五 ...

  3. ffplay.c学习-8-暂停、逐帧、⾳量

    ffplay.c学习-8-暂停.逐帧.⾳量 目录 播放.暂停 暂停/继续状态切换 暂停状态下的视频播放 暂停状态下的⾳频播放 逐帧.调⾳量.静⾳ 逐帧 调⾳量 静⾳ 常⽤的播放器操作: 播放:程序启动 ...

  4. ffplay.c学习-7-以音频同步为基准

    ffplay.c学习-7-以音频同步为基准 目录 以音频为基准 ⾳频主流程 视频主流程 delay的计算 以视频为基准 视频主流程 ⾳频主流程 synchronize_audio swr_set_co ...

  5. ffplay.c学习-6-⾳视频同步基础

    ffplay.c学习-6-⾳视频同步基础 目录 ⾳视频同步策略 ⾳视频同步概念 FFmpeg中的时间单位 ⾳视频时间换算的问题 不同结构体的time_base/duration分析 不同结构体的PTS ...

  6. ffplay.c学习-2-数据读取线程

    ffplay.c学习-2-数据读取线程 目录 准备⼯作 avformat_alloc_context 创建上下⽂ ic->interrupt_callback avformat_open_inp ...

  7. ffplay.c学习-1-框架及数据结构

    ffplay.c学习-1-框架及数据结构 目录 ffplay.c的意义 FFplay框架分析 数据结构分析 struct VideoState 播放器封装 struct Clock 时钟封装 stru ...

  8. ffplay.c学习-3-音视频解码线程

    ffplay.c学习-3-音视频解码线程 目录 解码线程 视频解码线程 video_thread() get_video_frame() 同⼀播放序列流连续的情况下,不断调⽤avcodec_recei ...

  9. 嵌入式学习——使用定时器输出PWM波形,实现 LED呼吸灯的效果

    嵌入式学习--使用定时器输出PWM波形,实现 LED呼吸灯的效果 目录 嵌入式学习--使用定时器输出PWM波形,实现 LED呼吸灯的效果 1. 任务要求 2 PWM波介绍, 2.1 什么是PWM(Pu ...

最新文章

  1. 【微服务架构】SpringCloud之断路器(hystrix)
  2. MongoDB入门简单介绍
  3. [转]OpenCL 教学(一)
  4. win10 基础之上安装 Linux-Manjaro-Deepin 连夜采坑,快速整理下
  5. 路由器端口转发linux服务器端口映射,路由器端口映射怎么设置?
  6. 汇编语言程序设计--基于ARM
  7. 大数据技术原理与应用-林子雨版-课后习题答案
  8. 学习笔记——物联网知识
  9. ROC(AUC)的显著性检验
  10. Android简易本地音乐播放器,简单实现Android本地音乐播放器
  11. 移动硬盘RAW格式,无法识别读取
  12. LightGBM 中文文档
  13. Android 和 iOS 实现录屏推流的方案整理
  14. 「入门运维必看」一篇让小白彻底搞懂性能调优!
  15. PTA 7-113 用switch语句编程百分制成绩转换为五分制成绩
  16. 应用程序编程太难?Appy Pie推出“零基础”VR AR设计平台
  17. 使用KlipC避开平台扫止损,控制止盈和延迟订单成交
  18. 安卓虚拟键盘_安卓这些年变化多惊人?老玩家的回忆杀
  19. Aiseesoft Mac Video Converter Ultimate for Mac(视频转换工具)
  20. 聚观早报 | iPhone 14正式官宣;支付宝、微信新增信用卡取现

热门文章

  1. 多线程安全问题产生解决方案
  2. 浅谈EntityFramework框架的使用
  3. 永乐XIANDAI 08
  4. Ecshop支付宝网银支付插件|支付宝网银直连插件|纯网关网银接口
  5. {在头值中找到无效的字符。} 发email的时候 遇到这个问题 老师解决
  6. Excel Oledb设置
  7. Linq Coding -- Part Eight (Equals Topic)
  8. Python高级语法-正则表达式
  9. bms中soh计算方式_BMS电池管理系统由浅入深全方位解析
  10. IOCP不可忽视的细节