聲明:文章內容僅代表個人觀點,不保證描述的準確性,僅供參考!
音頻的數據流程走向,有些描述不正確的,需要自己看代碼再理解理解了,先上個流程圖吧,湊合着看
文字描述
1、<audio-io.c> input_and_output (struct audio_output *audio, uint64_t audio_time, uint64_t prev_time)
獲取音頻數據
清空所有混音器audio->mixes中的每個混音器的buff,並逐個將混音器的每個聲道buff指針賦值給局部變量audio_output_data data[MAX_AUDIO_MIXES],
調用已綁定的回調函數 audio->input_cb (audio->input_param, prev_time, audio_time, &new_ts, active_mixes, data)獲取新的音頻數據
通過修改局部變量data中各個指針的buff內容,來完成修改 audio->mixes 中的每個混音器的buff內容
audio->input_cb回調函數綁定的接口爲 obs-audio.c 中的 audio_callback
2、<obs-audio.c> audio_callback (void *param,uint64_t start_ts_in, uint64_t end_ts_in, uint64_t *out_ts,uint32_t mixers, struct audio_output_data *mixes)
獲取音頻碼率 sample_rate ,音頻聲道 channels
遍歷當前場景中的所有源( source ),加入到音頻的渲染隊列 audio->render_order 中 push_audio_tree (NULL, source, audio);
以及 audio->root_nodes 中,da_push_back ( audio->root_nodes , &source );
把系統自帶的音頻輸入輸出音頻源(如音響和麥克風)加入到渲染隊列 audio->render_order 中
source = data->first_audio_source;
while (source) {
push_audio_tree(NULL, source, audio);
source = (struct obs_source*)source->next_audio_source;
}
循環調用接口 obs_source_audio_render 對渲染隊列中的所有源做音頻數據渲染,每個渲染完成的音頻數據存放在 source->audio_output_buf 中
3、<obs-source.c> void obs_source_audio_render(obs_source_t *source, uint32_t mixers,size_t channels, size_t sample_rate, size_t size)
如果綁定了音頻渲染器,則調用custom_audio_render:
static void custom_audio_render(obs_source_t *source, uint32_t mixers,size_t channels, size_t sample_rate)
將源的音頻輸出buff指針賦值給局部變量obs_source_audio_mix audio_data
audio_data.output[mix].data[ch] = source->audio_output_buf[mix][ch];
調用source->info.audio_render回調函數,填充audio_data中各個buff的內容,調用完成後source->audio_output_buf中的音頻數據填充完成
該回調綁定接口爲obs-scene.c中的scene_audio_render(),具體是如何獲取的沒再細看
否則調用process_audio_source_tick:
static inline void process_audio_source_tick(obs_source_t *source,uint32_t mixers, size_t channels, size_t sample_rate,size_t size)
這個就比較簡明瞭,將source->audio_input_buf中各個聲道的數據拷貝到source->audio_output_buf中
設置音頻輸出音量大小,apply_audio_volume
執行完成,返回audio_callback,繼續下一步
4、<obs-audio.c> audio_callback
處理混音:調用mix_audio(),根據audio->root_nodes隊列,循環處理將source->audio_output_buf中的音頻數據添加到每個與其相關的混音器中,傳入混音器集合的數組
,當前源,聲道,音頻採樣率,時間戳
static inline void mix_audio(struct audio_output_data *mixes,obs_source_t *source, size_t channels, size_t sample_rate, struct ts_info *ts)
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
for (size_t ch = 0; ch < channels; ch++) {
register float *mix = mixes[mix_idx].data[ch];
register float *aud = source->audio_output_buf[mix_idx][ch];
register float *end;
mix += start_point;
end = aud + total_floats;
while (aud < end)
*(mix++) += *(aud++);
}
}
混音處理完成後,即得到所有混音器中的已處理完成的混音數據,用於接下來進行的音頻編碼以及推流
5、<audio-io.c> input_and_output
執行完成audio->input_cb,並得到混音器的完整數據後,執行函數 clamp_audio_output 取締混音器中的所有數據爲[-1.0,1.0],
至於爲什麼要將數據設置成這樣,我也不知道。。。。
static inline void clamp_audio_output (struct audio_output *audio, size_t bytes)
for (size_t plane = 0; plane < audio->planes; plane++) {
float *mix_data = mix->buffer[plane];
float *mix_end = &mix_data[float_size];
while (mix_data < mix_end) {
float val = *mix_data;
val = (val > 1.0f) ? 1.0f : val;
val = (val < -1.0f) ? -1.0f : val;
*(mix_data++) = val;
}
}
遍歷所有混音器,調用 do_audio_output 接口,輸出音頻數據:
static inline void do_audio_output (struct audio_output *audio,size_t mix_idx, uint64_t timestamp, uint32_t frames)
將當前混音器的所有聲道的buff指針強轉成uint8_t*,賦值給局部變量audio_data data,更新data中的frames 和 時間戳
對數據進行重新採樣resample_audio_output,
mix->inputs數據存放的是輸出個數,包括錄像和推流,兩個輸出類型綁定的輸出回調函數不同,當前是以推流爲例
調用輸出回調函數input->callback進行音頻數據輸出,推流綁定的接口爲<obs-encoder.c>中的 receive_audio
以上是獲取混音完成後的數據,接下來是音頻編碼內容
6、 <obs-encoder.c> static void receive_audio (void *param, size_t mix_idx, struct audio_data *data)
判斷音頻第一次數據是否收到,收到第一次數據後,清空encoder->audio_input_buff
if (!encoder->first_received) {
encoder->first_raw_ts = data->timestamp;
encoder->first_received = true;
clear_audio(encoder);
}
調用 buffer_audio 將音頻數據添加到encoder的音頻數據緩存audio_input_buff
static bool buffer_audio(struct obs_encoder *encoder, struct audio_data *data)
檢查視頻數據是否已經收到,沒有收到視頻數據,不發送音頻數據
uint64_t v_start_ts = encoder->paired_encoder->start_ts;
/* no video yet, so don't start audio */
if (!v_start_ts) {
success = false;
goto fail;
}
檢查音視頻時間戳是否同步,下一幀音頻數據的時間戳小於當前視頻時間戳,說明音頻數據慢了,不發送音頻數據
end_ts += (uint64_t)data->frames * 1000000000ULL /
(uint64_t)encoder->samplerate;
if (end_ts <= v_start_ts) {
success = false;
goto fail;
}
音頻數據已開始,並且和視頻數據配對成功時,將音頻數據添加到encoder->audio_input_buff
push_back_audio(encoder, data ,size ,offset_size)
音頻數據大小大於一幀的音頻數據大小時,調用 send_audio_data 發送音頻數據
send_audio_data(encoder);
static void send_audio_data(struct obs_encoder *encoder)
取出音頻數據,存放在局部變量 encoder_frame enc_frame中,調用do_encode 執行推流前的音頻編碼
static inline void do_encode (struct obs_encoder *encoder, struct encoder_frame *frame)
創建待推流的數據包 struct encoder_packet pkt = {0}; 初始化數據包的基礎數據,包括計算幀率的分子和分母,編碼器
調用編碼器的回調函數進行編碼encoder->info.encode,當前例子中調用的時aac編碼器,回調接口爲模塊obs-ffmpeg.dll 中的
obs-ffmpeg-audio-encoders.c enc_encode
7、obs-ffmpeg.dll <obs-ffmpeg-audio-encoders.c> static bool enc_encode (void *data, struct encoder_frame *frame,struct encoder_packet *packet, bool*received_packet)
frame中的音頻數據拷貝到 enc_encoder->samples 中,調用 do_encode 接口編碼
static bool do_encode (struct enc_encoder *enc,struct encoder_packet *packet, bool *received_packet)
調用ffmpeg接口,對音頻數據進行編碼,編碼完成後數據存放在AVPacket avpacket中,再拷貝到enc->packet_buffer,並將packet_buffer的指針
賦值給待推流發送的數據包packet->data,設置packet中的相關參數,pts時間戳,dts編碼時間戳,size編碼後數據大小,時間基的分子和分母
此時已完成了音頻數據的推流所需的編碼處理,得到的packet即將用於音頻數據推流
8、libobs <obs-encoder.c> do_encode
完成編碼回調,使用系統時間而不是使用相對時間修改完成編碼的pkt中的dts_usec和sys_dts_usec
隨後,根據encoder->callback.num循環調用send_packet,至於這個callback爲什麼會是多個,以及什麼情況下會是多個,暫時猜測是包含推流和錄像的回調
static inline void send_packet (struct obs_encoder *encoder,struct encoder_callback *cb, struct encoder_packet *packet)
這裏在發送數據包之前,需要對視頻的第一幀發送進行單獨處理
如果發送的視頻第一幀,調用接口send_first_video_packet,在編碼數據的前段添加sei信息,然後再調用cb->new_packet回調函數
否則直接調用回調結構體中的cb->new_packet接口,該回調接口綁定至obs-output.c interleave_packets ,並且這個回調是音頻和視頻通用的回調
就是說音頻和視頻數據的推流都是在這裏執行數據包的發送
9、libobs <obs-output.c> static void interleave_packets (void *data, struct encoder_packet *packet)
獲取當前數據包對應的混音器的index並賦值給packet->track_idx,這個track_idx後面推流的時候會用到,他的作用還沒弄明白
調用obs_encoder_packet_create_instance(&out, packet);
拷貝packet中的數據到局部變量out中,其中進行malloc的時候,多申請了一個long類型長度的內存,這個pref是這個數據包的引用計數器
如果音視頻數據都收到時,調用apply_interleaved_packet_offset,這個函數是調整時間補償或者時間修復的嗎?
否則調用check_received接口將當前的音頻或視頻已收到標識設置爲true
was_started = output->received_audio && output->received_video;
......
if (was_started)
apply_interleaved_packet_offset(output, &out);
else
check_received(output, packet);
根據編碼時間戳,將當前數據包插入到輸出隊列中,並將output->highest_audio_ts設置爲當前數據包的編碼時間戳
insert_interleaved_packet(output, &out);
set_higher_ts(output, &out);
如果當前是否第一次收到了音頻以及視頻數據包,調用prune_interleaved_packets(output)對數據包中的內容進行修剪,修剪規則如下:
先找出第一幀音頻和第一幀視頻的數據包,以第一幀視頻數據包的index爲基準,對比兩個數據包的時間戳的差值:
如果音頻數據包的時間戳減去視頻數據包的時間戳的數值大於每幀視頻間隔的時間差
那麼需要刪除這個音頻數據包時間戳之前的所有音視頻數據包
如果沒有找到這樣的音頻數據包,那麼就需要找出音視頻數據包的時間戳差距最小的那個數據包的index,如果這個index的值比第一幀視頻數據包的index小
那麼需要刪除這個index之前的所有數據包,如果比第一幀視頻數據包的index大,那麼需要刪除第一幀視頻數據包之前的所有數據包
通過對第一次發送的音視頻數據包的裁剪後,當前的待發送數據包中第一幀音視頻數據包的時間戳的差距最小,以達到首次發送的音視頻數據是同步的
調整修正完成後的待發送數據包相關的時間戳,再次確保發送的第一幀音視頻數據包的準確性,並且重新調整待發送數據包的index
調用發送數據包函數 send_interleaved
如果是後續收到的音視頻數據包,則直接調用發送函數 send_interleaved
if (output->received_audio && output->received_video) {
if (!was_started) {
if (prune_interleaved_packets(output)) {
if (initialize_interleaved_packets(output)) {
resort_interleaved_packets(output);
send_interleaved(output);
}
}
} else {
send_interleaved(output);
}
}
static inline void send_interleaved (struct obs_output *output)
確認待發送數據包中的第一個數據包時間戳是合法的
if (!has_higher_opposing_ts(output, &out))
return;
把第一個數據包從隊列中移除
da_erase(output->interleaved_packets, 0);
如果是視頻數據包的話,在這裏統計總的發送幀數
if (out.type == OBS_ENCODER_VIDEO) {
output->total_frames++;
調用output->info.encoded_packed回調函數,回調到rtmp發送數據包接口中
10、<rtmp-stream.c> static void rtmp_stream_data (void *data, struct encoder_packet *packet)
判斷推流連接是否斷開以及是否處於活動狀態,將數據包的數據拷貝至局部變量new_packet,數據包的引用技術+1
將數據包添加到待推流數據塊中,視頻數據包:add_video_packet ,音頻數據包: add_packet
其中視頻數據包在添加之前,檢查是否有需要丟棄的幀,檢查完成後也是調用add_packet
add_packet ,將數據包追加在stream->packets的尾部
添加成功後,喚醒信號量stream->send_sem,該信號量控制推流發送數據包,喚醒成功後在send_thread線程函數中執行發送數據包的操作send_packet,執行RTMP_Write之前,調用flv_packet_mux將數據封包爲flv格式
static void *send_thread(void *data)
while (os_sem_wait(stream->send_sem) == 0)
......
send_packet(stream, &packet, false, packet.track_idx