obs video-io.c

发布时间:2023年12月24日

video_frame_init 讲解

/* messy code alarm
video_frame_init 函数用于初始化视频帧。它接受一个指向 struct video_frame 结构体的指针 frame,
视频格式 format,以及宽度 width 和高度 height。该函数根据视频格式的不同,计算出每个视频帧的大小,
并在堆上为帧数据分配内存空间。然后,根据视频格式的不同,设置帧数据的偏移量和行大小,并将这些信息填充到 frame 结构体中。
*/

void video_frame_init(struct video_frame *frame, enum video_format format,
		      uint32_t width, uint32_t height)
{
	size_t size;
	size_t offsets[MAX_AV_PLANES];
	int alignment = base_get_alignment();

	if (!frame)
		return;

	memset(frame, 0, sizeof(struct video_frame));
	memset(offsets, 0, sizeof(offsets));

	switch (format) {
	case VIDEO_FORMAT_NONE:
		return;

	case VIDEO_FORMAT_I420: {
		/* 计算 Y 分量的大小,基于帧的宽度和高度 */
		size = width * height;

		/* 根据指定的对齐方式对大小进行对齐 */
		ALIGN_SIZE(size, alignment);

		/* 设置 Y 分量的偏移量 */
		offsets[0] = size;

		/* 计算 U 和 V 分量的宽度和高度 */
		const uint32_t half_width = (width + 1) / 2;
		const uint32_t half_height = (height + 1) / 2;

		/* 计算 U 和 V 分量的区域大小 */
		const uint32_t quarter_area = half_width * half_height;

		/* 将 U 分量的大小添加到总大小中 */
		size += quarter_area;

		/* 根据指定的对齐方式对大小进行对齐 */
		ALIGN_SIZE(size, alignment);

		/* 设置 U 分量的偏移量 */
		offsets[1] = size;

		/* 将 V 分量的大小添加到总大小中 */
		size += quarter_area;

		/* 根据指定的对齐方式对大小进行对齐 */
		ALIGN_SIZE(size, alignment);

		/* 为 YUV 数据分配内存 */
		frame->data[0] = bmalloc(size);

		/* 设置 U 和 V 分量的指针 */
		frame->data[1] = (uint8_t *)frame->data[0] + offsets[0];
		frame->data[2] = (uint8_t *)frame->data[0] + offsets[1];

		/* 设置 YUV 数据的行大小 */
		frame->linesize[0] = width;
		frame->linesize[1] = half_width;
		frame->linesize[2] = half_width;

		break;
	}


}

init_cache

/*
这段代码是用于初始化视频输出缓存的函数。函数名为 init_cache,它接受一个指向 struct video_output 结构体的指针作为参数。

函数首先检查 video->info.cache_size 是否大于最大缓存大小 MAX_CACHE_SIZE,如果是,则将 video->info.cache_size 设置为
MAX_CACHE_SIZE。接下来,通过一个 for 循环,对 video->info.cache_size 次迭代,分别初始化缓存中的每个视频帧。在每次循环中,
函数会创建一个 video_frame 结构体指针 frame,该指针指向 video->cache[i],并使用 video_frame_init 函数初始化该帧。
初始化时,会将视频格式、宽度和高度信息填入帧中。最后,函数将 video->available_frames 设置为 video->info.cache_size,
表示缓存中有多少帧可用。
*/

static inline void init_cache(struct video_output *video)
{
	if (video->info.cache_size > MAX_CACHE_SIZE)
		video->info.cache_size = MAX_CACHE_SIZE;

	for (size_t i = 0; i < video->info.cache_size; i++) {
		struct video_frame *frame;
		frame = (struct video_frame *)&video->cache[i];

		video_frame_init(frame, video->info.format, video->info.width,
				 video->info.height);
	}

	video->available_frames = video->info.cache_size;
}

video_output_open

/*
这段代码是一个视频输出组件的初始化函数 video_output_open。它接受一个指向 video_t 指针的指针(用于返回视频输出对象的地址)和一个 video_output_info 结构体指针作为参数。

首先,函数会检查传入的 info 参数是否有效,如果无效则返回 VIDEO_OUTPUT_INVALIDPARAM,表示初始化失败。

接着,函数会动态分配内存以创建一个 video_output 结构体对象,并将 info 中的信息复制到这个对象中。如果内存分配失败,则函数会返回 VIDEO_OUTPUT_FAIL。

然后,函数会根据 info 中的帧率信息计算每一帧的时间间隔,并将结果存储在 frame_time 中。

接下来,函数会初始化两个递归互斥锁 data_mutex 和 input_mutex 以及一个信号量 update_semaphore。如果初始化失败,则函数会释放之前分配的资源,并返回 VIDEO_OUTPUT_FAIL。

紧接着,函数会创建一个线程 thread,并将其入口函数设置为 video_thread。如果线程创建失败,则函数会释放之前分配的资源,并返回 VIDEO_OUTPUT_FAIL。

最后,函数会调用 init_cache 函数对 out 进行初始化,并将 out 赋值给 *video,以便将视频输出对象的地址返回给调用者。最后,函数返回 VIDEO_OUTPUT_SUCCESS 表示初始化成功。

总的来说,video_output_open 函数的作用是根据传入的 video_output_info 初始化一个视频输出对象,并将其地址存储在 *video 中,同时返回初始化的结果。

*/

int video_output_open(video_t **video, struct video_output_info *info)
{
	struct video_output *out;

	if (!valid_video_params(info))
		return VIDEO_OUTPUT_INVALIDPARAM;

	out = bzalloc(sizeof(struct video_output));
	if (!out)
		goto fail0;

	memcpy(&out->info, info, sizeof(struct video_output_info));
	out->frame_time =
		util_mul_div64(1000000000ULL, info->fps_den, info->fps_num);

	if (pthread_mutex_init_recursive(&out->data_mutex) != 0)
		goto fail0;
	if (pthread_mutex_init_recursive(&out->input_mutex) != 0)
		goto fail1;
	if (os_sem_init(&out->update_semaphore, 0) != 0)
		goto fail2;
	if (pthread_create(&out->thread, NULL, video_thread, out) != 0)
		goto fail3;

	init_cache(out);

	*video = out;
	return VIDEO_OUTPUT_SUCCESS;

fail3:
	os_sem_destroy(out->update_semaphore);
fail2:
	pthread_mutex_destroy(&out->input_mutex);
fail1:
	pthread_mutex_destroy(&out->data_mutex);
fail0:
	bfree(out);
	return VIDEO_OUTPUT_FAIL;
}

video_thread

/*
 * 视频线程函数,用于处理视频输出
 *
 * param: 指向视频输出结构体的指针
 */
static void *video_thread(void *param)
{
    /* 将参数转换为视频输出结构体 */
    struct video_output *video = param;

    /* 设置线程名称为 "video-io: video thread" */
    os_set_thread_name("video-io: video thread");

    /* 获取视频线程的名称 */
    const char *video_thread_name =
        profile_store_name(obs_get_profiler_name_store(),
                           "video_thread(%s)", video->info.name);

    /* 在视频信号量上等待 */
    while (os_sem_wait(video->update_semaphore) == 0) {
        /* 如果视频已停止,则退出循环 */
        if (video->stop)
            break;

        /* 启动性能分析 */
        profile_start(video_thread_name);

        /* 在当前帧输出之前不断增加总帧数,直到当前帧输出成功 */
        while (!video->stop && !video_output_cur_frame(video)) {
            os_atomic_inc_long(&video->total_frames);
        }

        /* 当前帧输出成功后增加总帧数 */
        os_atomic_inc_long(&video->total_frames);

        /* 结束性能分析 */
        profile_end(video_thread_name);

        /* 重新启用线程性能分析 */
        profile_reenable_thread();
    }

    return NULL;
}


video_output_cur_frame

// 动态数组结构体,用于存储动态数组的信息
struct darray {
    void *array;    // 指向数组数据的指针
    size_t num;     // 数组中当前元素的数量
    size_t capacity; // 数组当前的容量
};

/*
 * 用于存储有关缓存视频帧的信息的结构。
 */
struct cached_frame_info {
	struct video_data frame; // 视频帧数据
	int skipped;             // 被跳过的帧数
	int count;               // 总帧数计数
};


/*
 * 视频输入结构体,用于描述视频输入设备的信息。
 */
struct video_input {
    struct video_scale_info conversion;  // 视频转换信息
    video_scaler_t *scaler;              // 视频缩放器
    struct video_frame frame[MAX_CONVERT_BUFFERS]; // 视频帧缓冲区
    int cur_frame;                        // 当前帧索引

    // 允许以主合成 FPS 的分数输出,例如,60 FPS 的 frame_rate_divisor = 1 变为 30 FPS
    //
    // 使用单独的计数器而不是使用余数计算,
    // 以便允许同时启动的“inputs”在相同的帧上启动,
    // 而使用余数计算则会使帧对齐取决于编码器启动时的总帧数
    uint32_t frame_rate_divisor;          // 帧率除数
    uint32_t frame_rate_divisor_counter;  // 帧率除数计数器

    void (*callback)(void *param, struct video_data *frame); // 回调函数指针
    void *param;                           // 参数
};

/*
 * 检查当前视频输出是否包含完
 * 整的帧并处理当前帧
 *
 * video: 视频输出结构体指针
 *
 * 返回值:如果当前帧完整则返回true,否则返回false
 */
static inline bool video_output_cur_frame(struct video_output *video)
{
    struct cached_frame_info *frame_info;
    bool complete;
    bool skipped;

    /* 获取视频数据前先锁定数据互斥锁 */
    pthread_mutex_lock(&video->data_mutex);

    /* 获取当前视频输出中第一个添加的帧信息 */
    frame_info = &video->cache[video->first_added];

    /* 解锁数据互斥锁 */
    pthread_mutex_unlock(&video->data_mutex);

    /* 锁定输入互斥锁以处理视频输入 */
    pthread_mutex_lock(&video->input_mutex);

    /* 遍历视频输入并处理帧数据 */
    for (size_t i = 0; i < video->inputs.num; i++) {
        struct video_input *input = video->inputs.array + i;
        struct video_data frame = frame_info->frame;

        // 使用显式计数器而不是求余来允许在相同时间启动的多个编码器
        // 在同一帧上启动
        uint32_t skip = input->frame_rate_divisor_counter++;
        if (input->frame_rate_divisor_counter == input->frame_rate_divisor)
            input->frame_rate_divisor_counter = 0;

        if (skip)
            continue;

        if (scale_video_output(input, &frame))
            input->callback(input->param, &frame);
    }

    /* 解锁输入互斥锁 */
    pthread_mutex_unlock(&video->input_mutex);

    /* 处理当前帧信息 */
    pthread_mutex_lock(&video->data_mutex);

    /* 更新当前帧的时间戳并检查当前帧是否已经完整 */
    frame_info->frame.timestamp += video->frame_time;
    complete = --frame_info->count == 0;
    skipped = frame_info->skipped > 0;

    /* 如果当前帧完整则更新视频输出的相关信息 */
    if (complete) {
        if (++video->first_added == video->info.cache_size)
            video->first_added = 0;

        if (++video->available_frames == video->info.cache_size)
            video->last_added = video->first_added;
    }
    /* 如果当前帧被跳过,则更新相关信息并增加跳过帧的计数 */
    else if (skipped) {
        --frame_info->skipped;
        os_atomic_inc_long(&video->skipped_frames);
    }

    /* 解锁数据互斥锁 */
    pthread_mutex_unlock(&video->data_mutex);

    /* 返回当前帧是否完整的标志 */
    return complete;
}

文章来源:https://blog.csdn.net/jinjie412/article/details/135115164
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。