~ehmry/nim_tox

ref: c4e39e7ae94e8e23be5e2c390f9e9bcef6f18b7d nim_tox/src/toxcore/av.nim -rw-r--r-- 18.8 KiB
c4e39e7aEmery Hemingway Refactor error checks 6 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# 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 ctoxCheck(ok, body: untyped) =
  block:
    var
      res {.inject.} = ok
      err {.inject.} = addr res
    body
    if res != ok:
      raise newException(ToxError, $res)

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.}
  ctoxCheck(TOXAV_ERR_NEW_OK) do:
    result = ToxAv(core: toxav_new(tox.core, 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.}
  ctoxCheck(TOXAV_ERR_CALL_OK) do:
    result = call(av.core, friend, (uint32)audioBitRate, (uint32)videoBitRate, 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.}
  ctoxCheck(TOXAV_ERR_ANSWER_OK) do:
    result = answer(av.core, friend, audioBitRate.uint32, videoBitRate.uint32, 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.}
  ctoxCheck(TOXAV_ERR_CALL_CONTROL_OK) do:
    result = call_control(av.core, friend, control, 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.}
  ctoxCheck(TOXAV_ERR_SEND_FRAME_OK) do:
    result = audio_send_frame(
        av.core, friend, pcm, sampleCount, channels, samplingRate, 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.}
  ctoxCheck(TOXAV_ERR_BIT_RATE_SET_OK) do:
    result = audio_set_bit_rate(av.core, friend, bitRate, 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.}
  ctoxCheck(TOXAV_ERR_SEND_FRAME_OK) do:
    result = video_send_frame(av.core, friend, width, height, y, u, v, 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.}
  ctoxCheck(TOXAV_ERR_BIT_RATE_SET_OK) do:
    result = video_set_bit_rate(av.core, friend, bitRate, 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)