From 8bd002fba2a3a5b08dbd2e901b1a01ac79de1d7b Mon Sep 17 00:00:00 2001
From: Karl Semich <0xloem@gmail.com>
Date: Fri, 21 Sep 2018 04:29:53 +0000
Subject: [PATCH] rtl-sdr/lib: Manually releasing async buffers

Small API added to allow sample buffers to be used after the
lifetime of the callback, and manually returned to librtlsdr.
Allows multithreaded apps to track USB buffer buildup without
requiring memcpy() be called in the callback.
---
 include/rtl-sdr.h | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/librtlsdr.c   | 38 +++++++++++++++++++++++++++++++++++---
 2 files changed, 85 insertions(+), 3 deletions(-)

diff --git a/include/rtl-sdr.h b/include/rtl-sdr.h
index 3ed13ae..1ef508d 100644
--- a/include/rtl-sdr.h
+++ b/include/rtl-sdr.h
@@ -331,6 +331,7 @@ RTLSDR_API int rtlsdr_set_offset_tuning(rtlsdr_dev_t *dev, int on);
  */
 RTLSDR_API int rtlsdr_get_offset_tuning(rtlsdr_dev_t *dev);
 
+
 /* streaming functions */
 
 RTLSDR_API int rtlsdr_reset_buffer(rtlsdr_dev_t *dev);
@@ -381,6 +382,55 @@ RTLSDR_API int rtlsdr_read_async(rtlsdr_dev_t *dev,
 RTLSDR_API int rtlsdr_cancel_async(rtlsdr_dev_t *dev);
 
 /*!
+ * Enable or disable automatic resubmission of async transfer buffers.
+ * By default this is enabled, but if disabled then buffers will not
+ * be refilled with data until passed to rtlsdr_release_manual().
+ *
+ * This allows applications to track buildup of streaming buffers
+ * by returning from the callback quickly, without requiring an
+ * extra memcpy to take the data out of the callback for processing.
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \param on 0 means disabled, 1 enabled
+ * \return 0 on success
+ */
+RTLSDR_API int rtlsdr_set_automatic_release(rtlsdr_dev_t *dev, int on);
+
+/*!
+* When automatic releases are disabled, release a long-lived buffer that
+* was passed to an async callback.  Such buffers will not be reused until
+* released.
+*
+* There must be enough released buffers submitted to libusb at all times
+* to accommodate driver and hardware communication latency.  The total
+* number of buffers filling this role, and the total number of buffers
+* unreleased, can be calculated:
+*
+*   buf_num = value passed to rtlsdr_read_async at stream start
+*   callback_calls = number of times a buf has been passed to callback
+*   buf_releases = number of successful calls to rtlsdr_release_manual()
+*
+*   bufs_reserved = callback_calls - buf_releases
+*   bufs_in_flight = buf_num - bufs_reserved
+*
+* Note that this calculation assumes that all reserved buffers have been
+* passed to the callback already.  It is only useful if the callback
+* returns very quickly every time.  If the callback waits, buffers
+* may accumulate in the waiting time, and only be found when it returns.
+*
+* Once bufs_in_flight falls too low, the integrity of the stream is
+* compromised as the device rewrites over its full buffer.  The cutoff
+* for this depends on latency in the operating system and hardware.
+*
+* Once bufs_in_flight hits zero, streaming will stop completely.
+*
+* \param dev the device handle given by rtlsdr_open()
+* \param buf a pointer to the start of the buffer
+* \return 0 on success
+*/
+RTLSDR_API int rtlsdr_release_manual(rtlsdr_dev_t *dev, unsigned char *buf);
+
+/*!
  * Enable or disable the bias tee on GPIO PIN 0.
  *
  * \param dev the device handle given by rtlsdr_open()
diff --git a/src/librtlsdr.c b/src/librtlsdr.c
index 433ed5b..f6d5541 100644
--- a/src/librtlsdr.c
+++ b/src/librtlsdr.c
@@ -103,6 +103,7 @@ struct rtlsdr_dev {
 	void *cb_ctx;
 	enum rtlsdr_async_status async_status;
 	int async_cancel;
+	int manual_release;
 	int use_zerocopy;
 	/* rtl demod context */
 	uint32_t rate; /* Hz */
@@ -1702,7 +1703,8 @@ static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer)
 		if (dev->cb)
 			dev->cb(xfer->buffer, xfer->actual_length, dev->cb_ctx);
 
-		libusb_submit_transfer(xfer); /* resubmit transfer */
+		if (!dev->manual_release)
+			libusb_submit_transfer(xfer); /* resubmit transfer */
 		dev->xfer_errors = 0;
 	} else if (LIBUSB_TRANSFER_CANCELLED != xfer->status) {
 #ifndef _WIN32
@@ -1753,7 +1755,8 @@ static int _rtlsdr_alloc_async_buffers(rtlsdr_dev_t *dev)
 
 	dev->use_zerocopy = 1;
 	for (i = 0; i < dev->xfer_buf_num; ++i) {
-		dev->xfer_buf[i] = libusb_dev_mem_alloc(dev->devh, dev->xfer_buf_len);
+		dev->xfer_buf[i] = libusb_dev_mem_alloc(dev->devh, dev->xfer_buf_len
+						+ sizeof(struct libusb_transfer *));
 
 		if (!dev->xfer_buf[i]) {
 			fprintf(stderr, "Failed to allocate zero-copy "
@@ -1780,7 +1783,8 @@ static int _rtlsdr_alloc_async_buffers(rtlsdr_dev_t *dev)
 	/* no zero-copy available, allocate buffers in userspace */
 	if (!dev->use_zerocopy) {
 		for (i = 0; i < dev->xfer_buf_num; ++i) {
-			dev->xfer_buf[i] = malloc(dev->xfer_buf_len);
+			dev->xfer_buf[i] = malloc(dev->xfer_buf_len +
+						sizeof(struct libusb_transfer *));
 
 			if (!dev->xfer_buf[i])
 				return -ENOMEM;
@@ -1873,6 +1877,9 @@ int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx,
 					  (void *)dev,
 					  BULK_TIMEOUT);
 
+		*(struct libusb_transfer **)(dev->xfer_buf[i] + dev->xfer_buf_len) =
+			dev->xfer[i];
+
 		r = libusb_submit_transfer(dev->xfer[i]);
 		if (r < 0) {
 			fprintf(stderr, "Failed to submit transfer %i\n"
@@ -1961,6 +1968,31 @@ int rtlsdr_cancel_async(rtlsdr_dev_t *dev)
 	return -2;
 }
 
+int rtlsdr_set_automatic_release(rtlsdr_dev_t *dev, int on)
+{
+	if (!dev)
+		return -1;
+
+	if (RTLSDR_INACTIVE != dev->async_status)
+		return -2;
+
+	dev->manual_release = !on;
+
+	return 0;
+}
+
+int rtlsdr_release_manual(rtlsdr_dev_t *dev, unsigned char *buf)
+{
+	struct libusb_transfer * xfer;
+
+	if (!dev)
+		return -1;
+
+	xfer = *(struct libusb_transfer **)(buf + dev->xfer_buf_len);
+
+	return libusb_submit_transfer(xfer);
+}
+
 uint32_t rtlsdr_get_tuner_clock(void *dev)
 {
 	uint32_t tuner_freq;
-- 
2.11.0

