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

简介

本文针对官方例程中的: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;
}
posted @ 2024-04-25 17:19  paw5zx  阅读(203)  评论(0编辑  收藏  举报