开源相机管理库Aravis例程学习(四)——multiple-acquisition-signal

aravis,multiple,acquisition,signal · 浏览次数 : 6

小编点评

The code you provided aims to solve the frame loss problem in capturing images from a camera using GObject and GStreamer. Here's a summary of the key points: **Initialization:** * It sets up a GMainLoop object and a GStream object for communication with the camera. * It initializes an AppData structure with a pointer to the GMainLoop, counter, and a GCamera object. **Acquisition Process:** * It starts an asynchronous acquisition process using `arv_camera_start_acquisition`. * It sets the acquisition mode to continuous and starts streaming the captured frames. * It pushes 5 frames of data into the stream using `arv_stream_push_buffer`. * It connects the stream's `new-buffer` signal to a callback function `new_buffer_cb`. **Callbacks and Main Loop:** * When a new frame is received on the stream, it is pushed to the buffer. * The `new_buffer_cb` function is called when a new frame arrives, allowing you to process it. * The main loop continues running until the acquisition is stopped or an error occurs. **Error Handling:** * It checks for errors throughout the process and prints them to the console. * It stops the acquisition when an error occurs and clears the GStream and GCamera objects. **Key Takeaways:** * The code captures frames from the camera asynchronously and processes them in a callback. * It uses a GStream to handle the communication between the GObject and GStreamer. * It handles errors gracefully and ensures proper cleaning up of resources. * It uses the `arv_stream_set_emit_signals` function to trigger the callback function when a new frame is available. **Improvements:** * You could add a mechanism to handle multiple new frame sources or cameras. * You could optimize the frame capture process by adjusting the buffer size and other parameters. * You could implement a more robust error handling and logging mechanism.

正文

简介

本文针对官方例程中的:02-multiple-acquisition-signal做简单的讲解。并简单介绍其中调用的g_main_loop_newg_main_loop_rung_main_loop_quitg_signal_connectarv_stream_set_emit_signals

aravis版本:0.8.31
操作系统:ubuntu-20.04
gcc版本:9.4.0

例程代码

这段代码使用Aravis的API,控制相机连续采集,并通过GLib的事件循环机制和GObject的信号系统异步地获取10个图像,主要操作步骤如下:

  • 连接相机
  • 设置采集模式为连续采集
  • 创建流对象,并向流对象的buffer池中添加buffer
  • 设置流对象信号回调函数,并使能流对象信号发射
  • 开始采集
  • 启动事件循环
  • 获取10张图像后关闭事件循环
  • 关闭流对象信号发射,释放资源

连续采集multiple-acquisition-main-thread不同的是,本例中使用GMainLoop(GLib的事件循环)来处理异步事件,图像获取过程是异步进行的。

/* SPDX-License-Identifier:Unlicense */

/* Aravis header */
#include <arv.h>
/* Standard headers */
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include "LogManager.h"

typedef struct {
	GMainLoop *main_loop;
	guint32 counter;
} AppData;

void new_buffer_cb (ArvStream *stream, void *user_data)
{
	ArvBuffer *buffer;
	AppData *app_data = static_cast<AppData*>(user_data);

	buffer = arv_stream_pop_buffer (stream);
	PAW_INFO("Acquired"<<arv_buffer_get_image_width(buffer)<<"x"<<arv_buffer_get_image_height(buffer)<< " buffer");

	arv_stream_push_buffer (stream, buffer);

	app_data->counter++;
	if (app_data->counter == 10)
		g_main_loop_quit (app_data->main_loop);
}

int main (int argc, char **argv)
{
	ArvCamera *camera;
	AppData app_data;
	GError *error = NULL;

	app_data.main_loop = g_main_loop_new (NULL, FALSE);
	app_data.counter = 0;

	//连接相机
	camera = arv_camera_new (NULL, &error);

	if (ARV_IS_CAMERA (camera)) {
		ArvStream *stream = NULL;

		printf ("Found camera '%s'\n", arv_camera_get_model_name (camera, NULL));
		//设置采集模式
		arv_camera_set_acquisition_mode (camera, ARV_ACQUISITION_MODE_CONTINUOUS, &error);
		//创建流对象
		if (error == NULL)
			stream = arv_camera_create_stream (camera, NULL, NULL, &error);

		if (ARV_IS_STREAM (stream)) {
			int i;
			size_t payload;

			//获取有效负载大小
			payload = arv_camera_get_payload (camera, &error);
			if (error == NULL) {
				//设置流对象的缓冲区数量
				for (i = 0; i < 5; i++)
					arv_stream_push_buffer (stream, arv_buffer_new (payload, NULL));
			}

			//设置流对象信号回调函数
			g_signal_connect (stream, "new-buffer", G_CALLBACK (new_buffer_cb), &app_data);
			//设置流对象发射信号
			//当流对象接收到新的缓冲区时,发射new-buffer信号
			arv_stream_set_emit_signals (stream, TRUE);

			//开始采集
			if (error == NULL)
				arv_camera_start_acquisition (camera, &error);
			
			//启动主循环
			PAW_INFO("start main loop");
			if (error == NULL)
				g_main_loop_run (app_data.main_loop);
			PAW_INFO("start main loop end");

			if (error == NULL)
				//停止采集
				arv_camera_stop_acquisition (camera, &error);

			arv_stream_set_emit_signals (stream, FALSE);
			g_clear_object (&stream);
		}
		
		g_clear_object (&camera);
	}
	
	g_main_loop_unref (app_data.main_loop);

	if (error != NULL) {
		/* En error happened, display the correspdonding message */
		printf ("Error: %s\n", error->message);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

注:PAW_INFO是我自定义的用于打印日志的宏

运行结果:

其中<>之间的是线程号。

函数说明

g_main_loop_new

简介:GLib的API,构造GMainLoop对象

GMainLoop* g_main_loop_new(GMainContext* context, gboolean is_running)

其中:
[in]context:一个GMainContext,如果为NULL,将使用全局默认的main上下文
[in]is_running:设置为TRUE表示循环正在运行。这不是很重要,因为只要后面调用g_main_loop_run()就会将其设置为TRUE。

g_main_loop_run

简介:GLib的API,运行一个主循环,直到在循环中调用g_main_loop_quit()

void g_main_loop_run(GMainLoop* loop)

g_main_loop_quit

简介:GLib的API,停止GMainLoop的运行。任何使用g_main_loop_run()开启的循环都将返回。

void g_main_loop_quit(GMainLoop* loop)

g_signal_connect

简介:GObject的宏,用于将信号处理器连接到特定对象的某个信号上。当一个信号被发出时,处理器将被同步调用。

#define g_signal_connect(instance, detailed_signal, c_handler, data)

arv_stream_set_emit_signals

简介:控制流对象信号发射。默认情况下流对象发射信号是禁用的,因为信号发射在性能上有一定开销而且在某些应用场景下是不需要的。

void arv_stream_set_emit_signals(ArvStream* stream, gboolean emit_signals)

Available since: 0.2.0

Q&A

回调函数的同步调用与异步调用

观察程序运行时的日志,可以发现new_buffer_cb的运行并不是在主线程中。

但是按照g_signal_connect的描述,回调函数应该是被同步调用,也就是说new_buffer_cb理论上应该在主线程被调用。
后来查看文档发现,在GObject的信号系统中,处理器的调用是同步的。当信号发射时,其关联的所有处理器会都会在发射信号的线程中按照它们被连接的顺序依次执行。

所以正确的应该是:处理器是在信号发射的线程被调用,而不是在处理器被注册时的线程。

在本例中,预定义的信号new-buffer的处理器new_buffer_cb被绑定在流对象上,这意味着每当流对象有一个新的buffer可用时,这个信号就会被发射,随后new_buffer_cb就被调用。而官方文档钟提到,流对象内部是使用一个单独的线程来监听数据的到达,因此信号是在这个单独的线程被发射的,也就是说回调函数也是在这个单独的线程被调用的,而不是在主线程中。

帧丢失问题

官方给出的例程中,先启动的相机采集,然后才开始事件循环。我认为这样的话会存在丢帧的问题,因为在事件循环启动并准备好处理接收到的图像之前,相机可能已经开始发送数据,如果数据流的缓冲不足或处理不及时,新的图像数据可能会覆盖还未处理的旧数据,或者直接被丢弃。

所以我对代码做了一些改动,改变调用顺序为先开启事件循环,然后再启动相机的采集,代码如下:

/* SPDX-License-Identifier:Unlicense */
/* Aravis header */
#include <arv.h>
/* Standard headers */
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include "LogManager.h"

typedef struct {
	GMainLoop *main_loop;
	guint32 counter;
	ArvCamera *camera;
} AppData;

gboolean start_acquisition_cb(gpointer user_data)
{
	AppData *app_data = static_cast<AppData*>(user_data);
    GError *error = NULL;

    arv_camera_start_acquisition(app_data->camera, &error);

    if (error != NULL) {
        printf("Error: %s\n", error->message);
        g_main_loop_quit(app_data->main_loop);
    }
    //只调用一次
    return FALSE; 
}

...

int main (int argc, char **argv)
{
	AppData app_data;
	GError *error = NULL;

	app_data.main_loop = g_main_loop_new (NULL, FALSE);
	app_data.counter = 0;

	app_data.camera = arv_camera_new (NULL, &error);

	if (ARV_IS_CAMERA (app_data.camera)) {
		ArvStream *stream = NULL;

		printf ("Found camera '%s'\n", arv_camera_get_model_name (app_data.camera, NULL));

		arv_camera_set_acquisition_mode (app_data.camera, ARV_ACQUISITION_MODE_CONTINUOUS, &error);

		if (error == NULL)
			stream = arv_camera_create_stream (app_data.camera, NULL, NULL, &error);

		if (ARV_IS_STREAM (stream)) {
			int i;
			size_t payload;

			payload = arv_camera_get_payload (app_data.camera, &error);
			if (error == NULL) {
				for (i = 0; i < 5; i++)
					arv_stream_push_buffer (stream, arv_buffer_new (payload, NULL));
			}

			g_signal_connect (stream, "new-buffer", G_CALLBACK (new_buffer_cb), &app_data);

            PAW_INFO("emit signals");
            arv_stream_set_emit_signals (stream, TRUE);
            PAW_INFO("emit signals end");
            
            /* if (error == NULL)
				arv_camera_start_acquisition (camera, &error); */
			
			//在主循环开始后尽快执行一次start_acquisition_cb
			g_idle_add(start_acquisition_cb, &app_data);
			
			PAW_INFO("start main loop");
			if (error == NULL)
				g_main_loop_run (app_data.main_loop);
			PAW_INFO("start main loop end");
			if (error == NULL)
				arv_camera_stop_acquisition (app_data.camera, &error);

			arv_stream_set_emit_signals (stream, FALSE);

			g_clear_object (&stream);
		}

		g_clear_object (&app_data.camera);
	}

	g_main_loop_unref (app_data.main_loop);

	if (error != NULL) {
		printf ("Error: %s\n", error->message);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

与开源相机管理库Aravis例程学习(四)——multiple-acquisition-signal相似的内容:

开源相机管理库Aravis例程学习(四)——multiple-acquisition-signal

目录简介例程代码函数说明g_main_loop_newg_main_loop_rung_main_loop_quitg_signal_connectarv_stream_set_emit_signalsQ&A回调函数的同步调用与异步调用帧丢失问题 简介 本文针对官方例程中的:02-multiple-

开源相机管理库Aravis例程学习(六)——camera-features

目录简介例程代码函数说明arv_camera_get_integerarv_camera_get_string 简介 本文针对官方例程中的:04-camera-features做简单的讲解。并介绍其中调用的arv_camera_get_integer,arv_camera_get_string。 a

开源相机管理库Aravis例程学习(五)——camera-api

目录简介例程代码函数说明arv_camera_get_regionarv_camera_get_pixel_format_as_stringarv_camera_get_pixel_formatARV_PIXEL_FORMAT_BIT_PER_PIXEL 简介 本文针对官方例程中的:03-camer

从零在win10上测试whisper、faster-whisper、whisperx在CPU和GPU的各自表现情况

Anaconda是什么? Anaconda 是一个开源的 Python 发行版本,主要面向数据科学、机器学习和数据分析等领域。它不仅包含了 Python 解释器本身,更重要的是集成了大量的用于科学计算、数据分析和机器学习相关的第三方库,并且提供了一个强大的包管理和环境管理工具——Conda。 通过C

驱动开发:内核文件读写系列函数

在应用层下的文件操作只需要调用微软应用层下的`API`函数及`C库`标准函数即可,而如果在内核中读写文件则应用层的API显然是无法被使用的,内核层需要使用内核专有API,某些应用层下的API只需要增加Zw开头即可在内核中使用,例如本章要讲解的文件与目录操作相关函数,多数ARK反内核工具都具有对文件的管理功能,实现对文件或目录的基本操作功能也是非常有必要的。

记 Codes 开源免费研发管理平台 —— 日报与工时融合集中式填报的创新实现

市面上所有的研发管理软件,大多都有工时相关功能,但是却没有日报功能,好像也没什么问题,但是在使用过程中体验非常不好,为什么呢? 项目管理对于基层工作人员来说,主要解决这三个问题:开展我的工作、协作我们的工作和汇报我的工作,也就是说日常的汇报也是刚需。平台没有日报就会有下面的问题。 第一、如果离开平台...

20.1K Star!Notion的开源替代方案:AFFiNE

Notion这款笔记软件相信很多开发者都比较熟悉了,很多读者,包括我自己都用它来记录和管理自己的笔记。今天给大家推荐一个最近比较火的开源替代方案:AFFiNE。目前该开源项目已经斩获20.1K Star,热度非常的高,下面一起来认识一下这个继Notion之后,被热捧的开源软件吧。 ![](https

成为钢铁侠!只需一块RTX3090,微软开源贾维斯(J.A.R.V.I.S.)人工智能AI助理系统

梦想照进现实,微软果然不愧是微软,开源了贾维斯(J.A.R.V.I.S.)人工智能助理系统,贾维斯(jarvis)全称为Just A Rather Very Intelligent System(只是一个相当聪明的人工智能系统),它可以帮助钢铁侠托尼斯塔克完成各种任务和挑战,包括控制和管理托尼的机甲装备,提供实时情报和数据分析,帮助托尼做出决策等等。 如今,我们也可以拥有自己的贾维斯人工智能助理

Docker V24 及 Docker Compose V2 的安装及使用

Docker 是一款流行的开源容器化平台,使用 Docker 可以有效地隔离应用程序和系统环境,使得应用程序在不同的环境中具有相同的行为 Docker Compose 是一个用于定义和管理多个容器工具

Known框架实战演练——进销存基础数据

本文介绍如何实现进销存管理系统的基础数据模块,基础数据模块包括商品信息、供应商管理和客户管理3个菜单页面。供应商和客户字段相同,因此可共用一个页面组件类。 项目代码:JxcLite 开源地址: https://gitee.com/known/JxcLite 1. 配置模块 运行项目,在【系统管理-模