obs-ndi-output.cpp
ndi推流,音视频延迟大,不同步,卡顿。参考obs-ndi推流代码。/*obs-ndiCopyright (C) 2016-2018 Stéphane Lepin <steph name of authorThis program is free software; you can redistribute it and/ormodify it under the terms of the
ndi推流,音视频延迟大,不同步,卡顿。参考obs-ndi推流代码。
https://github.com/Palakis/obs-ndi/blob/master/src/obs-ndi-output.cpp
/*
obs-ndi
Copyright (C) 2016-2018 Stéphane Lepin <steph name of author
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; If not, see <https://www.gnu.org/licenses/>
*/
#include <obs-module.h>
#include <util/platform.h>
#include <util/threading.h>
#include <util/profiler.h>
#include <util/circlebuf.h>
#include "obs-ndi.h"
static FORCE_INLINE uint32_t min_uint32(uint32_t a, uint32_t b)
{
return a < b ? a : b;
}
typedef void (*uyvy_conv_function)(uint8_t* input[], uint32_t in_linesize[],
uint32_t start_y, uint32_t end_y,
uint8_t* output, uint32_t out_linesize);
static void convert_i444_to_uyvy(uint8_t* input[], uint32_t in_linesize[],
uint32_t start_y, uint32_t end_y,
uint8_t* output, uint32_t out_linesize)
{
uint8_t* _Y;
uint8_t* _U;
uint8_t* _V;
uint8_t* _out;
uint32_t width = min_uint32(in_linesize[0], out_linesize);
for (uint32_t y = start_y; y < end_y; ++y) {
_Y = input[0] + ((size_t)y * (size_t)in_linesize[0]);
_U = input[1] + ((size_t)y * (size_t)in_linesize[1]);
_V = input[2] + ((size_t)y * (size_t)in_linesize[2]);
_out = output + ((size_t)y * (size_t)out_linesize);
for (uint32_t x = 0; x < width; x += 2) {
// Quality loss here. Some chroma samples are ignored.
*(_out++) = *(_U++); _U++;
*(_out++) = *(_Y++);
*(_out++) = *(_V++); _V++;
*(_out++) = *(_Y++);
}
}
}
struct ndi_output
{
obs_output_t *output;
const char* ndi_name;
bool uses_video;
bool uses_audio;
bool started;
NDIlib_send_instance_t ndi_sender;
uint32_t frame_width;
uint32_t frame_height;
NDIlib_FourCC_video_type_e frame_fourcc;
double video_framerate;
size_t audio_channels;
uint32_t audio_samplerate;
uint8_t* conv_buffer;
uint32_t conv_linesize;
uyvy_conv_function conv_function;
uint8_t* audio_conv_buffer;
size_t audio_conv_buffer_size;
os_performance_token_t* perf_token;
};
const char* ndi_output_getname(void* data)
{
UNUSED_PARAMETER(data);
return obs_module_text("NDIPlugin.OutputName");
}
obs_properties_t* ndi_output_getproperties(void* data)
{
UNUSED_PARAMETER(data);
obs_properties_t* props = obs_properties_create();
obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE);
obs_properties_add_text(props, "ndi_name",
obs_module_text("NDIPlugin.OutputProps.NDIName"), OBS_TEXT_DEFAULT);
return props;
}
void ndi_output_getdefaults(obs_data_t* settings)
{
obs_data_set_default_string(settings,
"ndi_name", "obs-ndi output (changeme)");
obs_data_set_default_bool(settings, "uses_video", true);
obs_data_set_default_bool(settings, "uses_audio", true);
}
bool ndi_output_start(void* data)
{
auto o = (struct ndi_output*)data;
uint32_t flags = 0;
video_t* video = obs_output_video(o->output);
audio_t* audio = obs_output_audio(o->output);
if (!video && !audio) {
blog(LOG_ERROR, "'%s': no video and audio available", o->ndi_name);
return false;
}
if (o->uses_video && video) {
video_format format = video_output_get_format(video);
uint32_t width = video_output_get_width(video);
uint32_t height = video_output_get_height(video);
switch (format) {
case VIDEO_FORMAT_I444:
o->conv_function = convert_i444_to_uyvy;
o->frame_fourcc = NDIlib_FourCC_video_type_UYVY;
o->conv_linesize = width * 2;
o->conv_buffer = new uint8_t[(size_t)height * (size_t)o->conv_linesize * 2]();
break;
case VIDEO_FORMAT_NV12:
o->frame_fourcc = NDIlib_FourCC_video_type_NV12;
break;
case VIDEO_FORMAT_I420:
o->frame_fourcc = NDIlib_FourCC_video_type_I420;
break;
case VIDEO_FORMAT_RGBA:
o->frame_fourcc = NDIlib_FourCC_video_type_RGBA;
break;
case VIDEO_FORMAT_BGRA:
o->frame_fourcc = NDIlib_FourCC_video_type_BGRA;
break;
case VIDEO_FORMAT_BGRX:
o->frame_fourcc = NDIlib_FourCC_video_type_BGRX;
break;
default:
blog(LOG_WARNING, "unsupported pixel format %d", format);
return false;
}
o->frame_width = width;
o->frame_height = height;
o->video_framerate = video_output_get_frame_rate(video);
flags |= OBS_OUTPUT_VIDEO;
}
if (o->uses_audio && audio) {
o->audio_samplerate = audio_output_get_sample_rate(audio);
o->audio_channels = audio_output_get_channels(audio);
flags |= OBS_OUTPUT_AUDIO;
}
NDIlib_send_create_t send_desc;
send_desc.p_ndi_name = o->ndi_name;
send_desc.p_groups = nullptr;
send_desc.clock_video = false; //此设置是关键
send_desc.clock_audio = false;
o->ndi_sender = ndiLib->send_create(&send_desc);
if (o->ndi_sender) {
if (o->perf_token) {
os_end_high_performance(o->perf_token);
}
o->perf_token = os_request_high_performance("NDI Output");
o->started = obs_output_begin_data_capture(o->output, flags);
if (o->started) {
blog(LOG_INFO, "'%s': ndi output started", o->ndi_name);
} else {
blog(LOG_ERROR, "'%s': data capture start failed", o->ndi_name);
}
} else {
blog(LOG_ERROR, "'%s': ndi sender init failed", o->ndi_name);
}
return o->started;
}
void ndi_output_stop(void* data, uint64_t ts)
{
auto o = (struct ndi_output*)data;
o->started = false;
obs_output_end_data_capture(o->output);
os_end_high_performance(o->perf_token);
o->perf_token = NULL;
ndiLib->send_destroy(o->ndi_sender);
if (o->conv_buffer) {
delete o->conv_buffer;
o->conv_function = nullptr;
}
o->frame_width = 0;
o->frame_height = 0;
o->video_framerate = 0.0;
o->audio_channels = 0;
o->audio_samplerate = 0;
}
void ndi_output_update(void* data, obs_data_t* settings)
{
auto o = (struct ndi_output*)data;
o->ndi_name = obs_data_get_string(settings, "ndi_name");
o->uses_video = obs_data_get_bool(settings, "uses_video");
o->uses_audio = obs_data_get_bool(settings, "uses_audio");
}
void* ndi_output_create(obs_data_t* settings, obs_output_t* output)
{
auto o = (struct ndi_output*)bzalloc(sizeof(struct ndi_output));
o->output = output;
o->started = false;
o->audio_conv_buffer = nullptr;
o->audio_conv_buffer_size = 0;
o->perf_token = NULL;
ndi_output_update(o, settings);
return o;
}
void ndi_output_destroy(void* data)
{
auto o = (struct ndi_output*)data;
if (o->audio_conv_buffer) {
bfree(o->audio_conv_buffer);
}
bfree(o);
}
void ndi_output_rawvideo(void* data, struct video_data* frame)
{
auto o = (struct ndi_output*)data;
if (!o->started || !o->frame_width || !o->frame_height)
return;
uint32_t width = o->frame_width;
uint32_t height = o->frame_height;
NDIlib_video_frame_v2_t video_frame = {0};
video_frame.xres = width;
video_frame.yres = height;
video_frame.frame_rate_N = (int)(o->video_framerate * 100);
video_frame.frame_rate_D = 100; // TODO fixme: broken on fractional framerates
video_frame.frame_format_type = NDIlib_frame_format_type_progressive;
video_frame.timecode = (int64_t)(frame->timestamp / 100);
video_frame.FourCC = o->frame_fourcc;
if (video_frame.FourCC == NDIlib_FourCC_type_UYVY) {
o->conv_function(frame->data, frame->linesize,
0, height,
o->conv_buffer, o->conv_linesize);
video_frame.p_data = o->conv_buffer;
video_frame.line_stride_in_bytes = o->conv_linesize;
}
else {
video_frame.p_data = frame->data[0];
video_frame.line_stride_in_bytes = frame->linesize[0];
}
ndiLib->send_send_video_v2(o->ndi_sender, &video_frame);
}
void ndi_output_rawaudio(void* data, struct audio_data* frame)
{
auto o = (struct ndi_output*)data;
if (!o->started || !o->audio_samplerate || !o->audio_channels)
return;
NDIlib_audio_frame_v3_t audio_frame = {0};
audio_frame.sample_rate = o->audio_samplerate;
audio_frame.no_channels = (int)o->audio_channels;
audio_frame.no_samples = frame->frames;
audio_frame.channel_stride_in_bytes = frame->frames * 4;
audio_frame.FourCC = NDIlib_FourCC_audio_type_FLTP;
const size_t data_size =
(size_t)audio_frame.no_channels * (size_t)audio_frame.channel_stride_in_bytes;
if (data_size > o->audio_conv_buffer_size) {
if (o->audio_conv_buffer) {
bfree(o->audio_conv_buffer);
}
o->audio_conv_buffer = (uint8_t*)bmalloc(data_size);
o->audio_conv_buffer_size = data_size;
}
for (int i = 0; i < audio_frame.no_channels; ++i) {
memcpy(o->audio_conv_buffer + ((size_t)i * (size_t)audio_frame.channel_stride_in_bytes),
frame->data[i],
audio_frame.channel_stride_in_bytes);
}
audio_frame.p_data = o->audio_conv_buffer;
audio_frame.timecode = NDIlib_send_timecode_synthesize;
ndiLib->send_send_audio_v3(o->ndi_sender, &audio_frame);
}
struct obs_output_info create_ndi_output_info()
{
struct obs_output_info ndi_output_info = {};
ndi_output_info.id = "ndi_output";
ndi_output_info.flags = OBS_OUTPUT_AV;
ndi_output_info.get_name = ndi_output_getname;
ndi_output_info.get_properties = ndi_output_getproperties;
ndi_output_info.get_defaults = ndi_output_getdefaults;
ndi_output_info.create = ndi_output_create;
ndi_output_info.destroy = ndi_output_destroy;
ndi_output_info.update = ndi_output_update;
ndi_output_info.start = ndi_output_start;
ndi_output_info.stop = ndi_output_stop;
ndi_output_info.raw_video = ndi_output_rawvideo;
ndi_output_info.raw_audio = ndi_output_rawaudio;
return ndi_output_info;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)