開源相機管理庫Aravis例程學習(四)——multiple-acquisition-signal

paw5zx發表於2024-04-25

目錄
  • 簡介
  • 例程程式碼
  • 函式說明
    • g_main_loop_new
    • g_main_loop_run
    • g_main_loop_quit
    • g_signal_connect
    • arv_stream_set_emit_signals
  • Q&A
    • 回撥函式的同步呼叫與非同步呼叫
    • 幀丟失問題

簡介

本文針對官方例程中的: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;
}

相關文章