uvgrtp-base/include/dispatch.hh

90 lines
3.6 KiB
C++

#pragma once
#include "runner.hh"
#include "util.hh"
#include <condition_variable>
#include <queue>
#include <thread>
#include <mutex>
namespace uvgrtp {
/* System call dispatcher is an optimization technique which aims to minimize
* the delay application experiences when calling push_frame().
*
* The push_frame() is divided roughly into frontend and backend:
* Frontend is the part that executes in the context of the calling application. During this
* phase, the HEVC frame is packetized into smaller frames and put into frame queue.
* On Linux this step is very fast, the application only loops through the HEVC frame and
* sets pointers to frame queue.
* On windows it's a little more work because we can't send multiple packets using one system call
* AND use scatter-gather I/O so it's either or.
*
* When the frame has been split into smaller chunks, the frontend will call the backend
* using trigger_send() functions. This function signals the dispatcher thread that there is
* a frame waiting to be sent. When trigger_send() returns, the application exists from the
* library code and the frame is sent in the background.
*
* By using a separate dispatcher thread, we're able to reduce the amount of delay application
* experiences to very small (<50 us even for large frames [>170 kB]) */
typedef struct transaction transaction_t;
class socket;
class dispatcher;
class dispatcher : public runner {
public:
dispatcher(uvgrtp::socket *socket);
~dispatcher();
/* Add new transaction to dispatcher's task queue
* The task queue is emptied in FIFO style */
rtp_error_t trigger_send(uvgrtp::transaction_t *transaction);
/* Create new thread object and start the dispatcher thread
*
* Return RTP_OK on success
* Return RTP_MEMORY_ERROR if allocation fails */
rtp_error_t start();
/* Stop the dispatcher thread
*
* Return RTP_OK on success
* Return RTP_NOT_READY if there are tasks to be processed in "tasks_" */
rtp_error_t stop();
/* Application and dispatcher communicate with each other using a condition variable
*
* If the task queue is empty, dispatcher will wait on a condition variable
* and when application pushes a new transaction to task queue, it will notify
* the dispatcher that it can start the send process. */
std::condition_variable& get_cvar();
/* When the stream is stopped, it is not a good idea to use condition variable to
* notify main thread that dispatcher is stopped because of the race condition between
* main thread's notify + wait and dispatcher's wait + notify
*
* The safest way is to lock the dispatcher mutex when waiting for dispatcher to stop
* and let it reopen it when it has sent all transactions */
std::mutex& get_mutex();
/* Get next transaction from task queue
* Return nullptr if the task queue is empty */
uvgrtp::transaction_t *get_transaction();
private:
static void dispatch_runner(uvgrtp::dispatcher *dispatcher, uvgrtp::socket *socket);
std::condition_variable cv_;
std::mutex d_mtx_;
std::queue<uvgrtp::transaction_t *> tasks_;
uvgrtp::socket *socket_;
};
};
namespace uvg_rtp = uvgrtp;