首页 » 软件开发 » 音视频推流端 OBS 框架学习和源码分析(数据包数据音频调用音视频)

音视频推流端 OBS 框架学习和源码分析(数据包数据音频调用音视频)

落叶飘零 2024-07-24 14:23:13 0

扫一扫用手机浏览

文章目录 [+]

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.各个source音频遍历:

接口:<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)

音视频推流端 OBS 框架学习和源码分析(数据包数据音频调用音视频) 软件开发
(图片来自网络侵删)

注释:获取音频码率 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 中;

C++音视频开发学习资料:点击领取→音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

3.各个source音频渲染:

接口:<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++);

}

}

混音处理完成后,即得到所有混音器中的已处理完成的混音数据,用于接下来进行的音频编码以及推流,回调到步骤1中obs-audio.c 中的 audio_callback;

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

注释:判断音频第一次数据是否收到,收到第一次数据后,清空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 发送音频数据

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.enc_encode

接口:<obs-ffmpeg-audio-encoders.c> static bool enc_encode (void data, struct encoder_frame frame,struct encoder_packet packet, boolreceived_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即将用于音频数据推流

接口: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 ,并且这个回调是音频和视频通用的回调 就是说音频和视频数据的推流都是在这里执行数据包的发送。

8.获取编码数据并发送

接口: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发送数据包接口中

9.推流输出

接口:<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

标签:

相关文章

语言中的借用,文化交融的桥梁

自古以来,人类社会的交流与发展离不开语言的传播。在漫长的历史长河中,各民族、各地区之间的文化相互碰撞、交融,产生了许多独特的语言现...

软件开发 2025-01-01 阅读1 评论0

机顶盒协议,守护数字生活的新卫士

随着科技的飞速发展,数字家庭逐渐走进千家万户。在这个时代,机顶盒成为了连接我们与丰富多彩的数字世界的重要桥梁。而机顶盒协议,作为保...

软件开发 2025-01-01 阅读1 评论0

语言基础在现代社会的重要性及方法步骤

语言是人类沟通的桥梁,是社会发展的基础。语言基础作为语言学习的基石,对于个人、社会乃至国家的发展具有重要意义。本文将从语言基础在现...

软件开发 2025-01-01 阅读2 评论0

粤语电影,传承文化,点亮时代之光

粤语电影,作为中国电影产业的一朵奇葩,以其独特的地域特色、丰富的文化内涵和鲜明的艺术风格,赢得了广大观众的喜爱。本文将从粤语电影的...

软件开发 2025-01-01 阅读5 评论0

苹果游戏语言,塑造未来娱乐体验的基石

随着科技的飞速发展,游戏产业逐渐成为全球娱乐市场的重要支柱。在我国,游戏产业更是蓬勃发展,吸引了无数玩家和投资者的目光。而在这其中...

软件开发 2025-01-01 阅读1 评论0