日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2024/4/11 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ffplay.c学习-4-⾳频输出和⾳频重采样 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ffplay.c學習-4-?頻輸出和?頻重采樣


目錄

  • ?頻輸出模塊
  • 打開SDL?頻設備
  • 打開?頻設備audio_open
  • 回調函數邏輯sdl_audio_callback
  • 回調函數讀取數據
  • ?頻重采樣
  • 重采樣邏輯
  • 樣本補償

  • 1. ?頻輸出模塊

  • 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的過程,最多只是執?了重采樣(數據源和輸出參數不?致時則做重采樣)。
  • 1. 打開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函數):
  • #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;
  • 由于不同的?頻輸出設備?持的參數不同,?軌的參數不?定能被輸出設備?持(此時就需要重采樣了), 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 函數內了。
  • 2. 打開?頻設備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 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

  • 再來看 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);} }
  • 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回調進?填充 時,實際上 是有開始被播放,所以我們這?采?的相對時間是,剛回調產?的,就是內部 在播放的時候,那相對時間實際也在?.
  • 最終
  • 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. 回調函數讀取數據

  • 接下來看下 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; }
  • audio_decode_frame 并沒有真正意義上的 decode 代碼,最多是進?了重采樣。主流程有以下步驟:
  • 從sampq取?幀,必要時丟幀。如發?了seek,此時serial會不連續,就需要丟幀處理
  • 計算這?幀的字節數。通過av_samples_get_buffer_size可以?便計算出結果
  • 獲取這?幀的數據。對于frame格式和輸出設備不同的,需要重采樣;如果格式相同,則直接拷?指針輸出即可。總之,需要在audio_buf中保存與輸出設備格式相同的?頻數據
  • 更新audio_clock,audio_clock_serial。?于設置audclk.

  • 2. ?頻重采樣

  • FFmpeg解碼得到的?頻幀的格式未必能被SDL?持,在這種情況下,需要進??頻重采樣,即將?頻幀格式轉換為SDL?持的?頻格式,否則是?法正常播放的。
  • ?頻重采樣涉及兩個步驟:
  • 打開?頻設備時進?的準備?作:確定SDL?持的?頻格式,作為后期?頻重采樣的?標格式。這?部分內容參考?頻輸出模塊
  • ?頻播放線程中,取出?頻幀后,若有需要(?頻幀格式與SDL?持?頻格式不匹配)則進?重采樣,否則直接輸出
  • 1. 重采樣邏輯

  • ?頻重采樣在 audio_decode_frame() 中實現, audio_decode_frame() 就是從?頻frame隊列中取出?個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沒有初始化) {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. 樣本補償

  • 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-⾳频输出和⾳频重采样的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。