Commit 6addd435 authored by Éric Thiébaut's avatar Éric Thiébaut
Browse files

Clarify and document the management of acquisition buffers

parent 348ef4c5
......@@ -500,8 +500,8 @@ static tao_status on_set_config(
if (src->bufferencoding != dst->bufferencoding) {
tao_encoding enc = src->bufferencoding;
int idx = -1;
for (int k = 0; k < dev->nencodings; ++k) {
if (dev->encodings[k] == enc) {
for (int k = 0; k < dev->nencs; ++k) {
if (dev->encs[k] == enc) {
idx = k;
break;
}
......@@ -592,7 +592,7 @@ static tao_status on_start(
return TAO_ERROR;
}
tao_encoding bufferencoding = dev->encodings[idx];
tao_encoding bufferencoding = dev->encs[idx];
// Get size of acquisition buffers in bytes.
status = AT_GetInt(dev->handle, L"ImageSizeBytes", &ival);
......@@ -713,28 +713,6 @@ static tao_status on_stop(
return TAO_OK;
}
static tao_status on_release_buffer(
tao_camera* cam)
{
// FIXME: is this an error if there are no pending buffers?
if (cam->pending > 0) {
andor_device* dev = (andor_device*)cam;
int index = TAO_INDEX_OF_FIRST_PENDING_ACQUISITION_BUFFER(cam);
int status = AT_QueueBuffer(dev->handle,
(AT_U8*)cam->bufs[index].data,
( int)cam->bufs[index].size);
if (status != AT_SUCCESS) {
// FIXME: Not being able to re-queue the buffer probably require
// more elaborated handling than just reporting an error: we should
// stop acquisition and resume it or something similar.
andor_store_error("AT_QueueBuffer", status);
return TAO_ERROR;
}
--cam->pending;
}
return TAO_OK;
}
static tao_status on_wait_buffer(
tao_camera* cam,
double secs)
......@@ -742,14 +720,16 @@ static tao_status on_wait_buffer(
// Get Andor device.
assert(cam->ops == &ops);
assert(cam->runlevel == 2);
assert(cam->pending >= 0);
assert(cam->pending < cam->nbufs);
andor_device* dev = (andor_device*)cam;
// Convert timeout value to milliseconds.
double msecs = round(1e3*secs);
unsigned int tm = (msecs >= AT_INFINITE ?
(unsigned int)AT_INFINITE :
(unsigned int)msecs);
unsigned int tm = AT_INFINITE;
double msecs = round(TAO_MILLISECONDS_PER_SECOND*secs);
if (msecs < tm) {
tm = msecs;
}
// Wait for a new image to be available.
int siz;
......@@ -774,7 +754,7 @@ static tao_status on_wait_buffer(
// Update last acquisition buffer (we know that
// cam->pending < cam->nbufs).
int last = (cam->last + 1)%cam->nbufs;
int last = TAO_NEXT_ACQUISTION_BUFFER(cam);
cam->bufs[last].data = ptr;
cam->bufs[last].size = siz;
cam->bufs[last].serial = ++cam->info.frames;
......@@ -786,6 +766,28 @@ static tao_status on_wait_buffer(
return TAO_OK;
}
static tao_status on_release_buffer(
tao_camera* cam)
{
assert(cam->ops == &ops);
assert(cam->runlevel == 2);
assert(cam->pending > 0);
andor_device* dev = (andor_device*)cam;
int index = TAO_FIRST_PENDING_ACQUISITION_BUFFER(cam);
int status = AT_QueueBuffer(dev->handle,
(AT_U8*)cam->bufs[index].data,
( int)cam->bufs[index].size);
if (status != AT_SUCCESS) {
// FIXME: Not being able to re-queue the buffer probably require
// more elaborated handling than just reporting an error: we should
// stop acquisition and resume it or something similar.
andor_store_error("AT_QueueBuffer", status);
return TAO_ERROR;
}
--cam->pending;
return TAO_OK;
}
const char * andor_name_of_encoding(
tao_encoding enc)
{
......@@ -838,14 +840,14 @@ tao_status andor_print_supported_encodings(
if (fprintf(out, "Supported pixel encodings: [") < 0) {
goto fprintf_error;
}
for (int k = 0; k < dev->nencodings; ++k) {
for (int k = 0; k < dev->nencs; ++k) {
if (k > 0) {
if (fputs(", ", out) < 0) {
goto fputs_error;
}
}
if (fprintf(out, "%s",
andor_name_of_encoding(dev->encodings[k])) < 0) {
andor_name_of_encoding(dev->encs[k])) < 0) {
goto fprintf_error;
}
}
......@@ -1040,11 +1042,11 @@ static tao_status get_pixelencoding(
andor_store_error("AT_GetInt(PixelEncoding)", status);
return TAO_ERROR;
}
if (idx < 0 || idx >= dev->nencodings) {
if (idx < 0 || idx >= dev->nencs) {
tao_store_error(__func__, TAO_OUT_OF_RANGE);
return TAO_ERROR;
}
*valptr = dev->encodings[idx];
*valptr = dev->encs[idx];
return TAO_OK;
}
......@@ -1121,9 +1123,8 @@ static tao_status alloc_buffers(
static void free_buffers(
andor_device* dev)
{
// Make sure the frame-grabber no longer use any buffers before
// effectively free the buffers. The former pending buffers, if any,
// are gone.
// Make sure the frame-grabber no longer use any buffers before effectively
// free the buffers. The former pending buffers, if any, are gone.
(void)AT_Flush(dev->handle);
dev->base.pending = 0;
andor_buffer* bufs = dev->bufs;
......@@ -1165,12 +1166,12 @@ static tao_status update_pixel_encodings(
status);
return TAO_ERROR;
}
dev->encodings[idx] = andor_wide_name_to_encoding(wstr);
dev->encs[idx] = andor_wide_name_to_encoding(wstr);
}
for (int idx = cnt; idx < ANDOR_MAX_ENCODINGS; ++idx) {
dev->encodings[idx] = TAO_ENCODING_UNKNOWN;
dev->encs[idx] = TAO_ENCODING_UNKNOWN;
}
dev->nencodings = cnt;
dev->nencs = cnt;
return TAO_OK;
}
......@@ -1238,8 +1239,8 @@ static tao_status check_configuration(
int idx = -1;
if (cfg->bufferencoding == cfg->sensorencoding) {
tao_encoding enc = cfg->sensorencoding;
for (int k = 0; k < dev->nencodings; ++k) {
if (dev->encodings[k] == enc) {
for (int k = 0; k < dev->nencs; ++k) {
if (dev->encs[k] == enc) {
idx = k;
break;
}
......
......@@ -74,19 +74,22 @@ struct andor_buffer {
int size;
};
/**
* Andor camera device.
*
* The Andor camera class extends the TAO camera class and the `base` member
* must come first. The other members are specific to all Andor cameras.
* Getting the type definition of the handle member is the only reason to
* include `<atcore.h>` here. The array of acquisition buffers is too track
* malloc'ed buffers.
*/
struct andor_device {
// The "Andor" camera class extends the TAO camera class and the "base"
// member must come first. The other members are specific to all "Andor"
// cameras. Getting the type definition of the handle member is the only
// reason to include <atcore.h> here. The array of acquisition buffers is
// too track malloc'ed buffers.
tao_camera base;/**< Unified TAO camera. */
AT_H handle;/**< Camera handle. */
tao_encoding encodings[ANDOR_MAX_ENCODINGS];
/**< Supported pixel encodings. */
int nencodings;/**< Number of supported pixel encodings. */
andor_buffer *bufs;/**< Array of acquisition buffers. */
int nbufs;/**< Number of acquisition buffers. */
tao_camera base;///< Unified TAO camera.
AT_H handle;///< Camera handle.
tao_encoding encs[ANDOR_MAX_ENCODINGS];///< Supported pixel encodings.
int nencs;///< Number of supported pixel encodings.
andor_buffer *bufs;///< Array of acquisition buffers.
int nbufs;///< Number of acquisition buffers.
};
// All known features (i.e. found in Andor SDK Documentation) are summarized in
......
......@@ -415,7 +415,7 @@ tao_status tao_camera_start_acquisition(
}
cam->events = 0;
cam->pending = 0;
cam->last = -1;
cam->last = -1; // so that index of next buffer is 0
// Call the "start" virtual method. In case of success, update the
// run-level and report success. In case of errors, the run-level is
......@@ -500,11 +500,10 @@ tao_status tao_camera_wait_acquisition_buffer(
return TAO_ERROR;
}
// Deal with pending buffers if any.
if (cam->pending >= 1 && drop >= 1) {
// Drop pending buffers in excess until there are no more than 1 (drop
// = 1) or 0 (drop > 1) pending buffers. Each dropped acquisition
// buffer counts as an overrun.
// Drop pending buffers in excess until there are no more than 1 (drop
// = 1) or 0 (drop > 1) pending buffers. Each dropped acquisition
// buffer counts as an overrun.
if (drop > 0) {
int limit = (drop > 1 ? 0 : 1);
while (cam->pending > limit) {
++cam->info.overruns;
......@@ -526,7 +525,7 @@ tao_status tao_camera_wait_acquisition_buffer(
}
// Return the first pending buffer.
int index = TAO_INDEX_OF_FIRST_PENDING_ACQUISITION_BUFFER(cam);
int index = TAO_FIRST_PENDING_ACQUISITION_BUFFER(cam);
*bufptr = &cam->bufs[index];
return TAO_OK;
}
......
......@@ -66,6 +66,47 @@ TAO_BEGIN_DECLS
*
* Initialization failure results in run-level being stuck at 0, subsequent
* operations on the camera yield @ref TAO_NOT_READY error code.
*
* Acquisition buffers are stored in a cyclic list. The following members of
* the camera structure are involved in the management of this list:
*
* - `cam->pending` = number of pending acquisition buffers waiting for being
* processed and released;
*
* - `cam->nbufs` = length of the cyclic list of acquistion buffers;
*
* - `cam->last` = index of last pending acquisition buffer in the cyclic list.
*
* When `cam->pending > 0`, the index of the first (oldest) pending acquisition
* buffer in the cyclic list is given by:
*
* ~~~~~
* next = (cam->last + 1)%cam->nbufs
* = TAO_NEXT_PENDING_ACQUISITION_BUFFER(cam)
* ~~~~~
*
* When `cam->pending > 0`, the index of the next (last + 1) acquisition buffer
* in the cyclic list is given by:
*
* ~~~~~
* first = (cam->nbufs + cam->last + 1 - cam->pending)%cam->nbufs
* = TAO_LAST_PENDING_ACQUISITION_BUFFER(cam)
* ~~~~~
*
* When acquisition start, tao_camera_start_acquisition() does the following:
*
* ~~~~~
* cam->pending = 0
* cam->last = cam->nbufs (or -1)
* ~~~~~
*
* During acquisition, if the `wait_buffer` callback effectively get a new
* acquisition buffer, it shall increment `cam->last` and `cam->pending`. Note
* that `cam->last` has to be incremented circularly, use the macro
* @ref TAO_NEXT_PENDING_ACQUISITION_BUFFER for that.
*
* During acquisition, if the `release_buffer` callback effectively release the
* first acquisition buffer, it shall decrement `cam->pending`.
*/
struct tao_camera_ops_ {
const char* name; ///< Camera model/family name.
......@@ -123,9 +164,9 @@ struct tao_camera_ops_ {
tao_status (*stop)(
tao_camera* cam);
///< Stop acquisition. This method shall stop acquisition immediately,
/// without waiting for the current frame. This method is only called when
/// the camera is acquiring. It shall return @ref TAO_OK on success or
/// @ref TAO_ERROR on failure.
/// without waiting for the current frame. This method is only called
/// when the camera is acquiring. It shall return @ref TAO_OK on success
/// or @ref TAO_ERROR on failure.
tao_status (*wait_buffer)(
tao_camera* cam,
......@@ -134,15 +175,22 @@ struct tao_camera_ops_ {
/// is acquiring. It shall not wait more than `secs` seconds. It shall
/// return @ref TAO_OK on success, @ref TAO_TIMEOUT on timeout or @ref
/// TAO_ERROR on failure. This method may assume that arguments have been
/// checked for correctness. This method shall update `cam->last` and
/// `cam->pending`.
/// checked for correctness. Whatever the result, this method shall
/// increment `cam->last` and `cam->pending` if a new pending acquisition
/// buffer has effectively been acquired (because it is not possible for
/// the caller to know whether the error prevented acquiring a new buffer
/// or not). Note that `cam->last` has to be incremented circularly, use
/// the macro @ref TAO_NEXT_PENDING_ACQUISITION_BUFFER for that.
tao_status (*release_buffer)(
tao_camera* cam);
///< Release the first pending acquisition buffer. This method is only
/// called when the camera is acquiring. It shall return @ref TAO_OK on
/// success or @ref TAO_ERROR on failure. This method shall update
/// `cam->last` and `cam->pending`.
/// called when the camera is acquiring and if there is at least one
/// pending buffer. It shall return @ref TAO_OK on success or @ref
/// TAO_ERROR on failure. Whatever the result, this method shall
/// decrement `cam->pending` if the first pending acquisition buffer has
/// effectively been released (because it is not possible for the caller
/// to know whether the error prevented releasing the buffer or not).
};
/**
......@@ -154,8 +202,8 @@ struct tao_camera_ops_ {
* to a camera are not sharable between processes.
*
* The number `nbufs` of acquisition buffers is set to `info.config.nbufs` at
* the latest when acquisition is started. The the minimum number of
* acquisition buffers should be 2.
* the latest when acquisition is started. The minimum number of acquisition
* buffers should be 2.
*
* @note The `runlevel` member may be related to the enumeration @ref tao_state
* but has fewer possible values because @ref tao_state can also
......@@ -181,23 +229,23 @@ struct tao_camera_ {
};
/**
* @def TAO_INDEX_OF_FIRST_PENDING_ACQUISITION_BUFFER(cam)
* @def TAO_FIRST_PENDING_ACQUISITION_BUFFER(cam)
*
* Yields the index of the first pending acquisition buffer of camera @b cam
* (modulo the number of buffers in the list). This macro uses its argument
* several times and should only be used when `cam->pending > 0` and
* `cam->nbufs > 0`.
*/
#define TAO_INDEX_OF_FIRST_PENDING_ACQUISITION_BUFFER(cam) \
(((cam)->last - (cam)->pending + 1 + (cam)->nbufs)%(cam)->nbufs)
#define TAO_FIRST_PENDING_ACQUISITION_BUFFER(cam) \
(((cam)->last + 1 - (cam)->pending + (cam)->nbufs)%(cam)->nbufs)
/**
* @def TAO_INDEX_OF_NEXT_ACQUISTION_BUFFER(cam)
* @def TAO_NEXT_ACQUISTION_BUFFER(cam)
*
* Yields the index of the next acquisition buffer in camera @b cam (just
* after the last one used to store the newest acquisition buffer so far).
*/
#define TAO_INDEX_OF_NEXT_ACQUISTION_BUFFER(cam) \
#define TAO_NEXT_ACQUISTION_BUFFER(cam) \
(((cam)->last + 1)%(cam)->nbufs)
/**
......
......@@ -1008,7 +1008,7 @@ static void* run_worker(
phnx_device* dev = (phnx_device*)cam;
if (drop) {
// Just keep the more recent buffer.
if (phnx_release_buffers_(dev, 1) != TAO_OK) {
if (phnx_release_buffers(dev, 1) != TAO_OK) {
tao_report_error(); // FIXME: should quit?
}
}
......@@ -1025,9 +1025,9 @@ static void* run_worker(
if (process != NULL) {
// Get index and address of first pending acquisition
// buffer.
int index = TAO_INDEX_OF_FIRST_PENDING_ACQUISITION_BUFFER(cam);
int index = TAO_FIRST_PENDING_ACQUISITION_BUFFER(cam);
tao_acquisition_buffer* buf = &cam->bufs[index];
buf->data = phnx_get_buffer_(dev);
buf->data = phnx_get_buffer(dev);
if (buf->data == NULL) {
tao_report_error(); // FIXME: should quit?
} else {
......@@ -1048,7 +1048,7 @@ static void* run_worker(
// In any case, the buffer should be considered as having been
// consumed so release the buffer.
if (phnx_release_buffers_(dev, cam->pending - 1) != TAO_OK) {
if (phnx_release_buffers(dev, cam->pending - 1) != TAO_OK) {
tao_report_error(); // FIXME: should quit?
}
}
......
......@@ -40,9 +40,6 @@ static void invalid_runlevel(
tao_camera* cam,
const char* func);
static tao_status update_buffer_address(
tao_camera* cam);
static void acquisition_callback(
tHandle handle,
uint32_t events,
......@@ -611,6 +608,72 @@ static tao_status on_stop(
return stop_acquisition((phnx_device*)cam, true);
}
//-----------------------------------------------------------------------------
// GET/RELEASE ACQUISITION BUFFER
//
// Something not specified in the doc. of the ActiveSilicon Phoenix library is
// that, when continuous acquisition and blocking mode are both enabled, all
// calls to `PHX_BUFFER_GET` yield the same image buffer until
// `PHX_BUFFER_RELEASE` is called. It seems that there is no needs to have a
// `PHX_BUFFER_GET` matches a `PHX_BUFFER_RELEASE` and that every
// `PHX_BUFFER_RELEASE` moves to the next buffer. However, acquisition buffers
// are used in their given order so it is not too difficult to figure out where
// we are if we count the number of frames.
//
// See doc. for `tao_camera_ops` and `tao_camera` for explanations about:
//
// cam->pending = number of pending acquisition buffers waiting for being
// processed and released;
// cam->nbufs = length of cyclic list of acquistion buffers;
// cam->last = index of last pending acquisition buffer in cyclic list;
//
// When acquisition start:
//
// pending = 0
// last = nbufs (or -1)
//
// When `cam->pending > 0`, the index of the first (oldest) pending acquisition
// buffer in the cyclic list is given by:
//
// next = (cam->last + 1)%cam->nbufs
// = TAO_NEXT_PENDING_ACQUISITION_BUFFER(cam)
//
// When `cam->pending > 0`, the index of the next (last + 1) acquisition buffer
// in the cyclic list is given by:
//
// first = (cam->nbufs + cam->last + 1 - cam->pending)%cam->nbufs
// = TAO_LAST_PENDING_ACQUISITION_BUFFER(cam)
//
// A successful call to `PHX_StreamRead(PHX_BUFFER_GET)` yields the first
// (oldest) pending buffer, at index given by
// `TAO_FIRST_PENDING_ACQUISITION_BUFFER`.
//
// A successful call to `PHX_StreamRead(PHX_BUFFER_RELEASE)` drops one pending
// buffer and unveil the new first pending buffer:
//
// --cam->pending
//
// which is done in phnx_release_buffers().
//
// Note that `cam->last` and `cam->pending` are incremented by the acquisition
// callback, not directly by `on_wait_buffer`, on reception of a "buffer ready"
// event:
//
// ++cam->pending
// cam->last = TAO_NEXT_PENDING_ACQUISITION_BUFFER(cam)
//
// Update address of first pending buffer.
static inline void update_first_pending_buffer_address(
tao_camera* cam)
{
// Retrieve address of first pending buffer.
void* data = phnx_get_buffer((phnx_device*)cam);
assert((cam->pending > 0) == (data != NULL));
int index = TAO_FIRST_PENDING_ACQUISITION_BUFFER(cam);
cam->bufs[index].data = data;
}
// Virtual method to wait for the next frame. This method is only called when
// the camera is acquiring. This method assumes that the camera has been
// locked by the caller. On success, the newly acquirred buffer is at updated
......@@ -622,6 +685,8 @@ static tao_status on_wait_buffer(
// Must be an acquiring Phoenix camera.
assert(cam->ops == &ops);
assert(cam->runlevel == 2);
assert(cam->pending >= 0);
assert(cam->pending < cam->nbufs);
// Compute absolute time-out time.
tao_time abstime;
......@@ -647,9 +712,9 @@ static tao_status on_wait_buffer(
// While there are no pending buffers, wait for events to be signaled or an
// error to occur. This is done in a `while` loop to cope with spurious
// signaled conditions. Index `cam->last` (and other members) is updated
// by the acquisition callback.
while (cam->pending < 1) { // FIXME: check all expected events
// signaled conditions. Index `cam->last`, number `cam->pending`, and
// other members are updated by the acquisition callback.
while (cam->pending < 1) {
if (forever) {
if (tao_condition_wait(&cam->notify, &cam->mutex) != TAO_OK) {
return TAO_ERROR;
......@@ -669,63 +734,38 @@ static tao_status on_wait_buffer(
}
// Get address of first pending acquisition buffer.
if (update_buffer_address(cam) != TAO_OK) {
return TAO_ERROR;
}
update_first_pending_buffer_address(cam);
return TAO_OK;
}
// Virtual method to release acquisition buffer. This method is only called
// when the camera is acquiring.
//
// Something not specified in the doc. of the ActiveSilicon Phoenix library is
// that, when continuous acquisition and blocking mode are both enabled, all
// calls to `PHX_BUFFER_GET` yield the same image buffer until
// `PHX_BUFFER_RELEASE` is called. It seems that there is no needs to have a
// `PHX_BUFFER_GET` matches a `PHX_BUFFER_RELEASE` and that every
// `PHX_BUFFER_RELEASE` moves to the next buffer. However, acquisition buffers
// are used in their given order so it is not too difficult to figure out where
// we are if we count the number of frames.
// when the camera is acquiring and when there are at least one pending
// acquisition buffer.
static tao_status on_release_buffer(
tao_camera* cam)
{
// This method is only called if cam->pending > 0 and
// cam->bufs[index].data != NULL and `index` is that of the first
// pending acquisition buffer.
// This method is only called for an acquiring Phoenix camera and if there
// is at least one pending acquisition buffer.
assert(cam->ops == &ops);
assert(cam->runlevel == 2);
assert(cam->pending > 0);
phnx_device* dev = (phnx_device*)cam;
if (cam->pending > 0) {
// cam->bufs[index].data != NULL and `index` is that of the first
// pending acquisition buffer.
#if NULLIFY_RELEASED_BUFFERS
int index = TAO_INDEX_OF_FIRST_PENDING_ACQUISITION_BUFFER(cam);
cam->bufs[index].data = NULL; // this buffer can no longer be used
int index = TAO_FIRST_PENDING_ACQUISITION_BUFFER(cam);
cam->bufs[index].data = NULL; // this buffer can no longer be used
#endif // NULLIFY_RELEASED_BUFFERS
if (phnx_release_buffers_(dev, cam->pending - 1) != TAO_OK) {
return TAO_ERROR;
}
if (cam->pending > 0) {
// If there is at least one more pending buffer, releasing the
// first (and oldest) one has unveiled the address of the next
// first (and oldest) one.
return update_buffer_address(cam);
}
}
return TAO_OK;
}
//-----------------------------------------------------------------------------
// GET ACQUISITION BUFFER
// Update address of first pending buffer.
static tao_status update_buffer_address(
tao_camera* cam)
{
// Retrieve address of first pending buffer.
void* data = phnx_get_buffer_((phnx_device*)cam);
int index = TAO_INDEX_OF_FIRST_PENDING_ACQUISITION_BUFFER(cam);
cam->bufs[index].data = data;
if (data == NULL && cam->pending > 0) {
if (phnx_release_buffers(dev, cam->pending - 1) != TAO_OK) {
return TAO_ERROR;
}
if (cam->pending > 0) {
// If there is at least one more pending buffer, releasing the first
// (and oldest) one has unveiled the address of the next first (and
// oldest) one.
update_first_pending_buffer_address(cam);
}
return TAO_OK;
}
......@@ -831,12 +871,12 @@ static void acquisition_callback(
// Get index of next informational buffer. We update all
// remaining information that is available at that time (not
// the buffer address though).
int next = (cam->last + 1)%cam->nbufs;
int next = TAO_NEXT_ACQUISTION_BUFFER(cam);
tao_acquisition_buffer* buf = &cam->bufs[next];
buf->frame_start = dev->frame_start;
buf->frame_end = dev->frame_end;
buf->buffer_ready = now;
buf->serial = cam->info.frames;
buf->serial = cam->info.frames; // FIXME:
cam->last = next;
cam->events |= TAO_EVENT_FRAME;
++cam->pending;
......@@ -873,7 +913,8 @@ static void acquisition_callback(
tao_camera_unlock(cam);
}
tao_status phnx_release_buffers_(
// FIXME: Make this static when old server code is no longer used.
tao_status phnx_release_buffers(
phnx_device* dev,
int limit)
{
......@@ -896,7 +937,8 @@ tao_status phnx_release_buffers_(
return TAO_OK;
}
void* phnx_get_buffer_(
// FIXME: Make this static when old server code is no longer used.
void* phnx_get_buffer(
phnx_device* dev)
{
stImageBuff vbuf;
......
......@@ -492,10 +492,10 @@ extern tao_encoding phnx_best_buffer_encoding(
/**
* Release some acquisition buffer of the Phoenix frame-grabber.
*
* This function releases pending acquisition buffers until there are at
* most a given number. This low-level function is supposed to work with
* the acquisition callback and with phnx_get_buffer_(). The number of
* pending buffers of the camera is updated.
* This function releases pending acquisition buffers until there are at most a
* given number. This low-level function is supposed to work with