Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Split toxav_iterate into audio and video part #1307

Merged
merged 2 commits into from
Dec 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion toxav/toxav.api.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ extern "C" {
* Only ${toxAV.iterate} is thread-safe, all other functions must run from the
* tox thread.
*
* Important exceptions are the `*_iterate` and `*_iterate_interval`
* functions. You have to choose either the single thread or the multi thread
* functions and read their documentation.
*
* A common way to run ToxAV (multiple or single instance) is to have a thread,
* separate from tox instance thread, running a simple ${toxAV.iterate} loop,
* sleeping for ${toxAV.iteration_interval} * milliseconds on each iteration.
Expand All @@ -56,6 +60,16 @@ extern "C" {
* error code.
*/

/** \subsection multi-threading Separate audio and video threads
*
* ToxAV supports either a single thread for audio and video or decoding and
* encoding them in separate threads. You have to choose one mode and can not
* mix function calls to those different modes.
*
* For best results use the multi-threaded mode and run the audio thread with
* higher priority than the video thread. This prioritizes audio over video.
*/

/**
* External Tox type.
*/
Expand Down Expand Up @@ -118,14 +132,15 @@ tox::this *tox { get(); }

/*******************************************************************************
*
* :: A/V event loop
* :: A/V event loop, single thread
*
******************************************************************************/


/**
* Returns the interval in milliseconds when the next toxav_iterate call should
* be. If no call is active at the moment, this function returns 200.
* This function MUST be called from the same thread as toxav_iterate.
*/
const uint32_t iteration_interval();

Expand All @@ -136,6 +151,43 @@ const uint32_t iteration_interval();
*/
void iterate();

/*******************************************************************************
*
* :: A/V event loop, multiple threads
*
******************************************************************************/

/**
* Returns the interval in milliseconds when the next toxav_audio_iterate call
* should be. If no call is active at the moment, this function returns 200.
* This function MUST be called from the same thread as toxav_audio_iterate.
*/
const uint32_t audio_iteration_interval();

/**
* Main loop for the session. This function needs to be called in intervals of
* toxav_audio_iteration_interval() milliseconds. It is best called in a
* separate thread from tox_iterate and toxav_video_iterate. The thread calling
* this function should have higher priority than the one calling
* toxav_video_iterate to prioritize audio over video.
*/
void audio_iterate();

/**
* Returns the interval in milliseconds when the next toxav_video_iterate call
* should be. If no call is active at the moment, this function returns 200.
* This function MUST be called from the same thread as toxav_video_iterate.
*/
const uint32_t video_iteration_interval();

/**
* Main loop for the session. This function needs to be called in intervals of
* toxav_video_iteration_interval() milliseconds. It is best called in a
* separate thread from tox_iterate and toxav_audio_iterate. The thread calling
* this function should have lower priority than the one calling
* toxav_audio_iterate to prioritize audio over video.
*/
void video_iterate();

/*******************************************************************************
*
Expand Down
139 changes: 107 additions & 32 deletions toxav/toxav.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
* VPX_DL_BEST_QUALITY (0) deadline parameter analogous to VPx BEST QUALITY mode.
*/

// iteration interval that is used when no call is active
#define IDLE_ITERATION_INTERVAL_MS 200

typedef struct ToxAVCall_s {
ToxAV *av;

Expand Down Expand Up @@ -64,6 +67,16 @@ typedef struct ToxAVCall_s {
struct ToxAVCall_s *next;
} ToxAVCall;


/** Decode time statistics */
typedef struct DecodeTimeStats_s {
int32_t count; /** Measure count */
int32_t total; /** Last cycle total */
int32_t average; /** Average decoding time in ms */

uint32_t interval; /** Calculated iteration interval */
} DecodeTimeStats;

struct ToxAV {
Tox *tox;
Messenger *m;
Expand Down Expand Up @@ -94,12 +107,9 @@ struct ToxAV {
toxav_video_bit_rate_cb *vbcb;
void *vbcb_user_data;

/** Decode time measures */
int32_t dmssc; /** Measure count */
int32_t dmsst; /** Last cycle total */
int32_t dmssa; /** Average decoding time in ms */

uint32_t interval; /** Calculated interval */
/* keep track of decode times for audio and video */
DecodeTimeStats audio_stats;
DecodeTimeStats video_stats;
Mono_Time *toxav_mono_time; /** ToxAV's own mono_time instance */
};

Expand All @@ -120,6 +130,19 @@ static ToxAVCall *call_remove(ToxAVCall *call);
static bool call_prepare_transmission(ToxAVCall *call);
static void call_kill_transmission(ToxAVCall *call);

/**
* @brief initialize d with default values
* @param d struct to be initialized, must not be nullptr
*/
static void init_decode_time_stats(DecodeTimeStats *d)
{
assert(d != nullptr);
d->count = 0;
d->total = 0;
d->average = 0;
d->interval = IDLE_ITERATION_INTERVAL_MS;
}

ToxAV *toxav_new(Tox *tox, Toxav_Err_New *error)
{
Toxav_Err_New rc = TOXAV_ERR_NEW_OK;
Expand Down Expand Up @@ -166,7 +189,8 @@ ToxAV *toxav_new(Tox *tox, Toxav_Err_New *error)
goto RETURN;
}

av->interval = 200;
init_decode_time_stats(&av->audio_stats);
init_decode_time_stats(&av->video_stats);
av->msi->av = av;

msi_register_callback(av->msi, callback_invite, MSI_ON_INVITE);
Expand Down Expand Up @@ -225,12 +249,48 @@ Tox *toxav_get_tox(const ToxAV *av)
{
return av->tox;
}

uint32_t toxav_audio_iteration_interval(const ToxAV *av)
{
return av->calls ? av->audio_stats.interval : IDLE_ITERATION_INTERVAL_MS;
}

uint32_t toxav_video_iteration_interval(const ToxAV *av)
{
return av->calls ? av->video_stats.interval : IDLE_ITERATION_INTERVAL_MS;
}

uint32_t toxav_iteration_interval(const ToxAV *av)
{
/* If no call is active interval is 200 */
return av->calls ? av->interval : 200;
return min_u32(toxav_audio_iteration_interval(av),
toxav_video_iteration_interval(av));
}
void toxav_iterate(ToxAV *av)

/**
* @brief calc_interval Calculates the needed iteration interval based on previous decode times
* @param av ToxAV struct to work on
* @param stats Statistics to update
* @param frame_time the duration of the current frame in ms
* @param start_time the timestamp when decoding of this frame started
*/
static void calc_interval(ToxAV *av, DecodeTimeStats *stats, int32_t frame_time, uint64_t start_time)
{
stats->interval = frame_time < stats->average ? 0 : (frame_time - stats->average);
stats->total += current_time_monotonic(av->m->mono_time) - start_time;

if (++stats->count == 3) {
stats->average = stats->total / 3 + 5; /* NOTE: Magic Offset for precision */
stats->count = 0;
stats->total = 0;
}
}

/**
* @brief common iterator function for audio and video calls
* @param av pointer to ToxAV structure of current instance
* @param audio if true, iterate audio, video else
*/
static void iterate_common(ToxAV *av, bool audio)
{
pthread_mutex_lock(av->mutex);

Expand All @@ -240,51 +300,66 @@ void toxav_iterate(ToxAV *av)
}

uint64_t start = current_time_monotonic(av->toxav_mono_time);
int32_t rc = 500;
// time until the first audio or video frame is over
int32_t frame_time = IDLE_ITERATION_INTERVAL_MS;

for (ToxAVCall *i = av->calls[av->calls_head]; i; i = i->next) {
if (i->active) {
pthread_mutex_lock(i->toxav_call_mutex);
pthread_mutex_unlock(av->mutex);
if (!i->active) {
continue;
}

pthread_mutex_lock(i->toxav_call_mutex);
pthread_mutex_unlock(av->mutex);

if (audio) {
ac_iterate(i->audio);
vc_iterate(i->video);

if (i->msi_call->self_capabilities & MSI_CAP_R_AUDIO &&
i->msi_call->peer_capabilities & MSI_CAP_S_AUDIO) {
rc = min_s32(i->audio->lp_frame_duration, rc);
frame_time = min_s32(i->audio->lp_frame_duration, frame_time);
}
} else {
vc_iterate(i->video);

if (i->msi_call->self_capabilities & MSI_CAP_R_VIDEO &&
i->msi_call->peer_capabilities & MSI_CAP_S_VIDEO) {
pthread_mutex_lock(i->video->queue_mutex);
rc = min_u32(i->video->lcfd, rc);
frame_time = min_u32(i->video->lcfd, frame_time);
pthread_mutex_unlock(i->video->queue_mutex);
}
}

uint32_t fid = i->friend_number;
uint32_t fid = i->friend_number;

pthread_mutex_unlock(i->toxav_call_mutex);
pthread_mutex_lock(av->mutex);
pthread_mutex_unlock(i->toxav_call_mutex);
pthread_mutex_lock(av->mutex);

/* In case this call is popped from container stop iteration */
if (call_get(av, fid) != i) {
break;
}
/* In case this call is popped from container stop iteration */
if (call_get(av, fid) != i) {
break;
}
}

av->interval = rc < av->dmssa ? 0 : (rc - av->dmssa);
av->dmsst += current_time_monotonic(av->toxav_mono_time) - start;
DecodeTimeStats *stats = audio ? &av->audio_stats : &av->video_stats;
calc_interval(av, stats, frame_time, start);
pthread_mutex_unlock(av->mutex);
}
void toxav_audio_iterate(ToxAV *av)
{
iterate_common(av, true);
}

if (++av->dmssc == 3) {
av->dmssa = av->dmsst / 3 + 5; /* NOTE Magic Offset 5 for precision */
av->dmssc = 0;
av->dmsst = 0;
}
void toxav_video_iterate(ToxAV *av)
{
iterate_common(av, false);
}

pthread_mutex_unlock(av->mutex);
void toxav_iterate(ToxAV *av)
{
toxav_audio_iterate(av);
toxav_video_iterate(av);
}

bool toxav_call(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate,
Toxav_Err_Call *error)
{
Expand Down
57 changes: 56 additions & 1 deletion toxav/toxav.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ extern "C" {
* Only toxav_iterate is thread-safe, all other functions must run from the
* tox thread.
*
* Important exceptions are the `*_iterate` and `*_iterate_interval`
* functions. You have to choose either the single thread or the multi thread
* functions and read their documentation.
*
* A common way to run ToxAV (multiple or single instance) is to have a thread,
* separate from tox instance thread, running a simple toxav_iterate loop,
* sleeping for toxav_iteration_interval * milliseconds on each iteration.
Expand All @@ -51,6 +55,15 @@ extern "C" {
* fail if mutexes are held by tox thread in which case they will set SYNC
* error code.
*/
/** \subsection multi-threading Separate audio and video threads
*
* ToxAV supports either a single thread for audio and video or decoding and
* encoding them in separate threads. You have to choose one mode and can not
* mix function calls to those different modes.
*
* For best results use the multi-threaded mode and run the audio thread with
* higher priority than the video thread. This prioritizes audio over video.
*/
/**
* External Tox type.
*/
Expand Down Expand Up @@ -132,7 +145,7 @@ Tox *toxav_get_tox(const ToxAV *av);

/*******************************************************************************
*
* :: A/V event loop
* :: A/V event loop, single thread
*
******************************************************************************/

Expand All @@ -141,6 +154,7 @@ Tox *toxav_get_tox(const ToxAV *av);
/**
* Returns the interval in milliseconds when the next toxav_iterate call should
* be. If no call is active at the moment, this function returns 200.
* This function MUST be called from the same thread as toxav_iterate.
*/
uint32_t toxav_iteration_interval(const ToxAV *av);

Expand All @@ -152,6 +166,47 @@ uint32_t toxav_iteration_interval(const ToxAV *av);
void toxav_iterate(ToxAV *av);


/*******************************************************************************
*
* :: A/V event loop, multiple threads
*
******************************************************************************/



/**
* Returns the interval in milliseconds when the next toxav_audio_iterate call
* should be. If no call is active at the moment, this function returns 200.
* This function MUST be called from the same thread as toxav_audio_iterate.
*/
uint32_t toxav_audio_iteration_interval(const ToxAV *av);

/**
* Main loop for the session. This function needs to be called in intervals of
* toxav_audio_iteration_interval() milliseconds. It is best called in a
* separate thread from tox_iterate and toxav_video_iterate. The thread calling
* this function should have higher priority than the one calling
* toxav_video_iterate to prioritize audio over video.
*/
void toxav_audio_iterate(ToxAV *av);

/**
* Returns the interval in milliseconds when the next toxav_video_iterate call
* should be. If no call is active at the moment, this function returns 200.
* This function MUST be called from the same thread as toxav_video_iterate.
*/
uint32_t toxav_video_iteration_interval(const ToxAV *av);

/**
* Main loop for the session. This function needs to be called in intervals of
* toxav_video_iteration_interval() milliseconds. It is best called in a
* separate thread from tox_iterate and toxav_audio_iterate. The thread calling
* this function should have lower priority than the one calling
* toxav_audio_iterate to prioritize audio over video.
*/
void toxav_video_iterate(ToxAV *av);


/*******************************************************************************
*
* :: Call setup
Expand Down