# SPDX-FileCopyrightText: 2020 Emery Hemingway
# SPDX-License-Identifier: GPL-3.0-or-later
import ../toxcore
{.pragma: ctoxHeader, header: "tox/toxav.h".}
{.pragma: ctoxError, ctoxHeader, importc: "Toxav_$1".}
{.pragma: ctoxEnum, ctoxHeader, importc: "TOXAV_$1".}
{.pragma: ctoxProc, ctoxHeader, importc: "toxav_$1".}
{.pragma: ctoxCallbackProc, ctoxHeader, importc: "toxav_callback_$1".}
{.pragma: ctoxConst, ctoxHeader, importc: "TOXAV_$1".}
template ctoxAssert(good, value: untyped) =
if value != good: raise newException(ToxError, $value)
type
Samples* = ptr UncheckedArray[int16]
## A unchecked buffer of PCM audio samples.
## 960 samples is a typical buffer size.
CbCall* = proc (friend: Friend; audioEnabled, videoEnabled: bool) {.gcsafe.}
## The function type for the call callback.
CbCallState* = proc (friend: Friend; state: uint32) {.gcsafe.}
## The function type for the call_state callback.
CbAudioBitRate* = proc (friend: Friend; audioBitRate: int) {.gcsafe.}
## The function type for the audio_bit_rate callback. The event is triggered
## when the network becomes too saturated for current bit rates at which
## point core suggests new bit rates.
CbVideoBitRate* = proc (
friend: Friend; videoBitRate: int) {.gcsafe.}
## The function type for the video_bit_rate callback. The event is triggered
## when the network becomes too saturated for current bit rates at which
## point core suggests new bit rates.
CbAudioReceiveFrame* = proc (
friend: Friend; pcm: Samples; sampleCount: int;
channels: uint8; samplingRate: uint32) {.gcsafe.}
## The function type for the audio_receive_frame callback. The callback can
## be called multiple times per single iteration depending on the amount of
## queued frames in the buffer. The received format is the same as in send
## function.
CbVideoReceiveFrame* = proc (
friend: Friend; width, height: uint16;
y, u, v: ptr uint8; ystride, ustride, vstride: int32) {.gcsafe.}
## The function type for the video_receive_frame callback.
##
## The size of plane data is derived from width and height as documented
## below.
##
## Strides represent padding for each plane that may or may not be present.
## You must handle strides in your image processing code. Strides are
## negative if the image is bottom-up hence why you MUST ``abs`` it when
## calculating plane buffer size.
CbGroupAudio* = proc (
group: Conference; peer: Peer; pcm: Samples;
samples: int; channels: uint8; sampleRate: uint32) {.gcsafe.}
AvCore = distinct pointer
## Opaque C type wrapped by the `ToxAV` object.
ToxAV* = ref object
## Tox A/V instance
core: AvCore
call: CbCall
callState: CbCallState
audioBitRate: CbAudioBitRate
videoBitRate: CbVideoBitRate
audioReceiveFrame: CbAudioReceiveFrame
videoReceiveFrame: CbVideoReceiveFrame
groupAudio: CbGroupAudio
template callbackSetter(name, body: untyped) =
## Magic for installing Nim closure callbacks into c-toxcore.
proc `on name`*(av: ToxAV; cb: `Cb name`) =
template callThru(args: varargs[untyped]) =
let av = cast[ToxAV](user_data)
if not av.name.isNil: av.name(args)
body
proc name(core: AvCore; callback: cbType; userData: ToxAV) {.ctoxCallbackProc.}
av.name = cb
if cb.isNil: `name`(av.core, nil, nil)
else: `name`(av.core, wrapper, av)
callbackSetter call:
type cbType = proc (
av: AvCore; friend: Friend; audioEnabled, videoEnabled: bool;
user_data: pointer) {.cdecl.}
proc wrapper(
av: AvCore; friend: Friend; audioEnabled, videoEnabled: bool;
user_data: pointer) {.cdecl.} =
callThru(friend, audioEnabled, videoEnabled)
callbackSetter call_state:
type cbType = proc (
av: AvCore; friend: Friend; state: uint32; userData: pointer) {.cdecl.}
proc wrapper(
av: AvCore; friend: Friend; state: uint32; userData: pointer) {.cdecl.} =
callThru(friend, state)
callbackSetter audio_bit_rate:
type cbType = proc (av: AvCore; friend: Friend; bitRate: uint32;
userData: pointer) {.cdecl.}
proc wrapper(av: AvCore; friend: Friend; bitRate: uint32;
userData: pointer) {.cdecl.} =
callThru(friend, (int)bitRate)
callbackSetter video_bit_rate:
type cbType = proc (av: AvCore; friend: Friend; bitRate: uint32;
userData: pointer) {.cdecl.}
proc wrapper(av: AvCore; friend: Friend; bitRate: uint32;
userData: pointer) {.cdecl.} =
callThru(friend, (int)bitRate)
callbackSetter audio_receive_frame:
type cbType = proc (av: AvCore; friend: Friend; pcm: ptr int16;
sampleCount: csize_t; channels: uint8; samplingRate: uint32;
userData: pointer) {.cdecl.}
proc wrapper(av: AvCore; friend: Friend; pcm: ptr int16; sampleCount: csize_t;
channels: uint8; samplingRate: uint32; userData: pointer) {.cdecl.} =
callThru(friend, cast[Samples](pcm), (int)sampleCount, channels, samplingRate)
callbackSetter video_receive_frame:
type cbType = proc (av: AvCore; friend: Friend;
width, height: uint16; y, u, v: ptr uint8;
ystride, ustride, vstride: int32; userData: pointer) {.cdecl.}
proc wrapper(av: AvCore; friend: Friend;
width, height: uint16; y, u, v: ptr uint8;
ystride, ustride, vstride: int32; userData: pointer) {.cdecl.} =
callThru(friend, width, height, y, u, v, ystride, ustride, vstride)
callbackSetter group_audio:
type cbType = proc (av: AvCore; group: Conference; peer: Peer; pcm: ptr int16;
samples: uint32; channels: uint8; sampleRate: uint32;
userData: pointer) {.cdecl.}
proc wrapper(av: AvCore; group: Conference; peer: Peer; pcm: ptr int16;
samples: uint32; channels: uint8; sampleRate: uint32;
userData: pointer) {.cdecl.} =
callThru(group, peer, cast[Samples](pcm), (int)samples, channels, sampleRate)
type Err_New* {.ctoxError.} = enum
TOXAV_ERR_NEW_OK,
## The function returned successfully.
TOXAV_ERR_NEW_NULL,
## One of the arguments to the function was NULL when it was not expected.
TOXAV_ERR_NEW_MALLOC,
## Memory allocation failure while trying to allocate structures required
## for the A/V session.
TOXAV_ERR_NEW_MULTIPLE
## Attempted to create a second session for the same Tox instance.
proc newAv*(tox: Tox): ToxAV =
## Start new A/V session. There can only be only one session per Tox instance.
proc toxav_new(tox: Core; error: ptr Err_New): AvCore {.
importc, ctoxHeader.}
var err: ERR_NEW
result = ToxAv(core: toxav_new(tox.core, addr err))
ctoxAssert(TOXAV_ERR_NEW_OK, err)
proc kill*(av: ToxAV) =
proc kill(av: AvCore) {.ctoxProc.}
kill(av.core)
## Releases all resources associated with the A/V session.
##
## If any calls were ongoing, these will be forcibly terminated without
## notifying peers. After calling this function, no other functions may be
## called and the av pointer becomes invalid.
proc toxav_get_tox(av: AvCore): Core {.ctoxProc.}
## Returns the Tox instance the A/V object was created for.
proc iterationInterval*(av: ToxAV): int =
## Returns the interval in milliseconds when the next ``iterate`` call
## should be. If no call is active at the moment, this function returns 200.
proc iteration_interval(av: AvCore): uint32 {.ctoxProc.}
(int)iterationInterval(av.core)
proc iterate*(av: ToxAV) =
## Main loop for the session. This function needs to be called in intervals of
## ``iterationInterval`` milliseconds. It is best called in the separate
## thread from tox_iterate.
proc iterate(av: AvCore) {.ctoxProc.}
iterate(av.core)
type Err_Call* {.ctoxError.} = enum
TOXAV_ERR_CALL_OK,
## The function returned successfully.
TOXAV_ERR_CALL_MALLOC,
## A resource allocation error occurred while trying to create the
## structures required for the call.
TOXAV_ERR_CALL_SYNC,
## Synchronization error occurred.
TOXAV_ERR_CALL_FRIEND_NOT_FOUND,
## The friend number did not designate a valid friend.
TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED,
## The friend was valid, but not currently connected.
TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL,
## Attempted to call a friend while already in an audio or video call with
## them.
TOXAV_ERR_CALL_INVALID_BIT_RATE
## Audio or video bit rate is invalid.
proc call*(av: ToxAV; friend: Friend; audioBitRate = 0; videoBitRate = 0): bool =
## Call a friend. This will start ringing the friend.
##
## It is the client's responsibility to stop ringing after a certain timeout,
## if such behaviour is desired. If the client does not stop ringing, the
## library will not stop until the friend is disconnected. Audio and video
## receiving are both enabled by default.
proc call(av: AvCore; friend: Friend; audio_bit_rate: uint32;
video_bit_rate: uint32; error: ptr Err_Call): bool {.ctoxProc.}
var err: Err_Call
result = call(av.core, friend, (uint32)audioBitRate, (uint32)videoBitRate, addr err)
ctoxAssert(TOXAV_ERR_CALL_OK, err)
type Err_Answer* {.ctoxError.} = enum
TOXAV_ERR_ANSWER_OK,
## The function returned successfully.
TOXAV_ERR_ANSWER_SYNC,
## Synchronization error occurred.
TOXAV_ERR_ANSWER_CODEC_INITIALIZATION,
## Failed to initialize codecs for call session. Note that codec initiation
## will fail if there is no receive callback registered for either audio or
## video.
TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND,
## The friend number did not designate a valid friend.
TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING,
## The friend was valid, but they are not currently trying to initiate a call.
## This is also returned if this client is already in a call with the friend.
TOXAV_ERR_ANSWER_INVALID_BIT_RATE
## Audio or video bit rate is invalid.
proc answer*(av: ToxAV; friend: Friend;
audioBitRate = 0;
videoBitRate = 0): bool =
## Accept an incoming call.
##
## If answering fails for any reason, the call will still be pending and it is
## possible to try and answer it later. Audio and video receiving are both
## enabled by default.
assert(av.audioReceiveFrame != nil or av.videoReceiveFrame != nil)
proc answer(
av: AvCore; friend: Friend; audio_bit_rate: uint32;
video_bit_rate: uint32; error: ptr Err_Answer): bool {.ctoxProc.}
var err: Err_Answer
result = answer(av.core, friend, audioBitRate.uint32, videoBitRate.uint32, addr err)
ctoxAssert(TOXAV_ERR_ANSWER_OK, err)
var
FRIEND_CALL_STATE_NONE* {.ctoxConst.}: uint32
## The empty bit mask. None of the bits specified below are set.
FRIEND_CALL_STATE_ERROR* {.ctoxConst.}: uint32
## Set by the AV core if an error occurred on the remote end or if friend
## timed out. This is the final state after which no more state
## transitions can occur for the call. This call state will never be triggered
## in combination with other call states.
FRIEND_CALL_STATE_FINISHED* {.ctoxConst.}: uint32
## The call has finished. This is the final state after which no more state
## transitions can occur for the call. This call state will never be
## triggered in combination with other call states.
FRIEND_CALL_STATE_SENDING_A* {.ctoxConst.}: uint32
## The flag that marks that friend is sending audio.
FRIEND_CALL_STATE_SENDING_V* {.ctoxConst.}: uint32
## The flag that marks that friend is sending video.
FRIEND_CALL_STATE_ACCEPTING_A* {.ctoxConst.}: uint32
## The flag that marks that friend is receiving audio.
FRIEND_CALL_STATE_ACCEPTING_V* {.ctoxConst.}: uint32
## The flag that marks that friend is receiving video.
type
CALL_CONTROL* {.ctoxEnum.} = enum
## Resume a previously paused call. Only valid if the pause was caused by this
## client, if not, this control is ignored. Not valid before the call is accepted.
TOXAV_CALL_CONTROL_RESUME,
## The function returned successfully.
TOXAV_CALL_CONTROL_PAUSE,
## Put a call on hold. Not valid before the call is accepted.
TOXAV_CALL_CONTROL_CANCEL,
## Reject a call if it was not answered, yet. Cancel a call after it was
## answered.
TOXAV_CALL_CONTROL_MUTE_AUDIO,
## Request that the friend stops sending audio. Regardless of the friend's
## compliance, this will cause the audio_receive_frame event to stop being
## triggered on receiving an audio frame from the friend.
TOXAV_CALL_CONTROL_UNMUTE_AUDIO,
## Calling this control will notify client to start sending audio again.
TOXAV_CALL_CONTROL_HIDE_VIDEO,
## Request that the friend stops sending video. Regardless of the friend's
## compliance, this will cause the video_receive_frame event to stop being
## triggered on receiving a video frame from the friend.
TOXAV_CALL_CONTROL_SHOW_VIDEO
## Calling this control will notify client to start sending video again.
type Err_Call_Control* {.ctoxError.} = enum
TOXAV_ERR_CALL_CONTROL_OK,
## The function returned successfully.
TOXAV_ERR_CALL_CONTROL_SYNC,
## Synchronization error occurred.
TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND,
## The friend passed did not designate a valid friend.
TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL,
## This client is currently not in a call with the friend. Before the call is
## answered, only CANCEL is a valid control.
TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION
## Happens if user tried to pause an already paused call or if trying to
## resume a call that is not paused.
proc callControl*(av: ToxAV; friend: Friend; control: CallControl): bool =
## Sends a call control command to a friend.
proc call_control(
av: AvCore; friend: Friend; control: CALL_CONTROL;
error: ptr Err_Call_Control): bool {.ctoxProc.}
var err: Err_Call_Control
result = call_control(av.core, friend, control, addr err)
ctoxAssert(TOXAV_ERR_CALL_CONTROL_OK, err)
type Err_Bit_Rate_Set* {.ctoxError.} = enum
TOXAV_ERR_BIT_RATE_SET_OK,
## The function returned successfully.
TOXAV_ERR_BIT_RATE_SET_SYNC,
## Synchronization error occurred.
TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE,
## The bit rate passed was not one of the supported values.
TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND,
## The friend passed did not designate a valid friend.
TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL
## This client is currently not in a call with the friend.
type Err_Send_Frame* {.ctoxError.} = enum
TOXAV_ERR_SEND_FRAME_OK,
## The function returned successfully.
TOXAV_ERR_SEND_FRAME_NULL,
## In case of video, one of Y, U, or V was NULL. In case of audio, the samples
## data pointer was NULL.
TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND,
## The friend passed did not designate a valid friend.
TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL,
## This client is currently not in a call with the friend.
TOXAV_ERR_SEND_FRAME_SYNC,
## Synchronization error occurred.
TOXAV_ERR_SEND_FRAME_INVALID,
## One of the frame parameters was invalid. E.g. the resolution may be too
## small or too large, or the audio sampling rate may be unsupported.
TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED,
## Either friend turned off audio or video receiving or we turned off sending
## for the said payload.
TOXAV_ERR_SEND_FRAME_RTP_FAILED
## Failed to push frame through rtp interface.
proc audioSendFrame*(
av: ToxAv; friend: Friend; pcm: ptr int16;
sampleCount: csize_t; channels: uint8;
samplingRate: uint32; ): bool =
## Send an audio frame to a friend.
##
## The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]...
## Meaning: sample 1 for channel 1, sample 1 for channel 2, ...
## For mono audio, this has no meaning, every sample is subsequent. For stereo,
## this means the expected format is LRLRLR... with samples for left and right
## alternating.
##
## Valid number of samples here are ((sample rate) * (audio length) / 1000),
## where audio length can be 2.5, 5, 10, 20, 40 or 60 millseconds.
##
## Supported number of channels are 1 and 2.
##
## Valid sampling rates are 8000, 12000, 16000, 24000, or 48000.
proc audio_send_frame(
av: AvCore; friend: Friend; pcm: ptr int16;
sample_count: csize_t; channels: uint8;
sampling_rate: uint32;
error: ptr Err_Send_Frame): bool {.ctoxProc.}
var err: Err_Send_Frame
result = audio_send_frame(av.core, friend, pcm, sampleCount, channels,
samplingRate, addr err)
ctoxAssert(TOXAV_ERR_SEND_FRAME_OK, err)
proc audioSetBitRate*(av: ToxAv; friend: Friend; bitRate: uint32): bool =
## Set the bit rate to be used in subsequent video frames.
## Bit rate is in Kb/sec. Set to 0 to disable.
proc audio_set_bit_rate(
av: AvCore; friend: Friend;
bit_rate: uint32; error: ptr Err_Bit_Rate_Set): bool {.ctoxProc.}
var err: Err_Bit_Rate_Set
result = audio_set_bit_rate(av.core, friend, bitRate, addr err)
ctoxAssert(TOXAV_ERR_BIT_RATE_SET_OK, err)
proc videoSendFrame*(
av: ptr ToxAV; friend: Friend;
width, height: uint16; y, u, v: ptr uint8): bool =
## Send a video frame to a friend.
##
## Y - plane should be of size: height * width
##
## U - plane should be of size: (height/2) * (width/2)
##
## V - plane should be of size: (height/2) * (width/2)
proc video_send_frame(
av: AvCore; friend: Friend; width: uint16;
height: uint16; y, u, v: ptr uint8; error: ptr Err_Send_Frame): bool {.ctoxProc.}
var err: Err_Send_Frame
result = video_send_frame(av.core, friend, width, height, y, u, v, addr err)
ctoxAssert(TOXAV_ERR_SEND_FRAME_OK, err)
proc videoSetBitRate*(av: ToxAv; friend: Friend; bitRate: uint32): bool =
## Set the bit rate to be used in subsequent video frames.
## Bit rate is in Kb/sec. Set to 0 to disable.
proc video_set_bit_rate(
av: AvCore; friend: Friend;
bit_rate: uint32; error: ptr Err_Bit_Rate_Set): bool {.ctoxProc.}
var err: Err_Bit_Rate_Set
result = video_set_bit_rate(av.core, friend, bitRate, addr err)
ctoxAssert(TOXAV_ERR_BIT_RATE_SET_OK, err)
proc groupSendAudio*(
av: ToxAv; group: Conference; pcm: ptr int16;
samples: int; channels: uint8; sampleRate: uint32): bool =
## Send audio to the group chat.
##
## Note that total size of pcm in bytes is equal to
## (samples * channels * sizeof(int16)).
##
## Valid number of samples are ((sample rate) * (audio length (Valid ones are:
## 2.5, 5, 10, 20, 40 or 60 ms)) / 1000)
## Valid number of channels are 1 or 2.
## Valid sample rates are 8000, 12000, 16000, 24000, or 48000.
##
## Recommended values are: samples = 960, channels = 1, sample_rate = 48000
proc group_send_audio(
tox: AvCore; group: Conference; pcm: ptr int16;
samples: cuint; channels: uint8; sample_rate: uint32): cint {.ctoxProc.}
let res = group_send_audio(av.core, group, pcm, (cuint)samples, channels, sampleRate)
res == 0
proc groupchatDisableAv*(av: ToxAv; group: Conference): bool =
## Disable A/V in a groupchat.
proc groupchat_disable_av(tox: AvCore; group: Conference): cint {.ctoxProc.}
let res = groupchat_disable_av(av.core, group)
res == 0
proc groupchatAvEnabled*(av: ToxAv; group: Conference): bool =
## Return whether A/V is enabled in the groupchat.
proc groupchat_av_enabled(tox: AvCore; group: Conference): bool {.ctoxProc.}
groupchat_av_enabled(av.core, group)