~ehmry/nim_opusenc

nim_opusenc/src/opusenc.nim -rw-r--r-- 9.4 KiB
7f0c91bcEmery Hemingway Manage lifetime of C objects with destructors 1 year, 1 month 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
# Copyright (c) 2020 Xiph.Org

import options

{.passC: staticExec("pkg-config --cflags opus libopusenc").}
{.passL: staticExec("pkg-config --libs opus libopusenc").}

{.pragma: opeHeader, header: "opus/opusenc.h".}
{.pragma: opeProc, opeHeader, importc: "ope_$1".}
{.pragma: opeConst, opeHeader, importc: "OPE_$1".}

var
  API_VERSION* {.opeConst.}: cint
    ## API version for this module.

  OK* {.opeConst.}: cint
  BAD_ARG* {.opeConst.}: cint
  INTERNAL_ERROR* {.opeConst.}: cint
  UNIMPLEMENTED* {.opeConst.}: cint
  ALLOC_FAIL* {.opeConst.}: cint
  CANNOT_OPEN* {.opeConst.}: cint
  TOO_LATE* {.opeConst.}: cint
  INVALID_PICTURE* {.opeConst.}: cint
  INVALID_ICON* {.opeConst.}: cint
  WRITE_FAIL* {.opeConst.}: cint
  CLOSE_FAIL* {.opeConst.}: cint

proc strerror(error: cint): cstring {.opeProc.}
  ## Converts a libopusenc error code into a human readable string.

proc get_version_string*(): cstring {.opeProc.}
  ## Returns a string representing the version
  ## of libopusenc being used at run time.

proc get_abi_version*(): cint {.opeProc.}

type OpusEncError* = object of CatchableError

template opeCheck(body: untyped) =
  var
    res {.inject.}: cint
    err {.inject.} = addr res
  body
  if res != OK:
    raise newException(OpusEncError, $strerror(res))

type
  ope_write_func = proc (user_data: pointer; `ptr`: ptr cuchar;
      len: int32): cint {.cdecl.}
  ope_close_func = proc (user_data: pointer): cint {.cdecl.}
  ope_packet_func = proc (user_data: pointer; packet_ptr: ptr cuchar;
                        packet_len: int32; flags: uint32) {.cdecl.}

  OpusEncCallbacks {.bycopy.} = object
    write*: ope_write_func
      ## Callback for writing to the stream.
    close*: ope_close_func
      ## Callback for closing the stream.

  Callbacks* = tuple
    write: proc (userData: pointer; buf: pointer; len: int): int {.gcsafe.}
    close: proc (userData: pointer): int {.gcsafe.}

  OpeComments = distinct pointer
  OpeEncoder = distinct pointer

  Comments* = object
    ## Opaque comments object.
    ope: OpeComments

  Encoder* = object
    ## Opaque encoder object.
    ope: OpeEncoder

  Family* = enum
    monoStereo = 0, surround = 1

proc initComments*(): Comments =
  ## Create a new comments object.
  proc comments_create(): OpeComments {.opeProc.}
  result.ope = comments_create()

proc `=destroy`*(com: var Comments) =
  if not com.ope.pointer.isNil:
    proc comments_destroy(p: OpeComments) {.opeProc.}
    comments_destroy(com.ope)

proc `=`*(a: var Comments; b: Comments) =
  if a.ope.pointer != b.ope.pointer:
    `=destroy`(a)
    proc comments_copy(comments: OpeComments): OpeComments {.opeProc.}
    a.ope = comments_copy(b.ope)

proc add*(com: Comments; tag, val: string) =
  ## Add a comment.
  proc comments_add(comments: OpeComments; tag: cstring;
      val: cstring): cint {.opeProc.}
  opeCheck:
    res = comments_add(com.ope, tag, val)

proc add*(com: Comments; tagAndVal: string) =
  ## Add a comment as a single tag=value string.
  proc comments_add_string(comments: OpeComments;
      tag_and_val: cstring): cint {.opeProc.}
  opeCheck:
    res = comments_add_string(com.ope, tagAndVal)

proc addPicture*(com: Comments; filename: string;
                              kind = -1; description = "") =
  ## Add a picture from a file.
  proc comments_add_picture(comments: OpeComments; filename: cstring;
                                picture_type: cint;
                                    description: cstring): cint {.opeProc.}
  opeCheck:
    let desc = if description == "": (cstring) nil else: (cstring)description
    res = comments_add_picture(com.ope, filename, (cint)kind, desc)

proc addPicture*(com: Comments;
    data: pointer; size: int; kind = -1; description = "") =
  ## Add a picture already in memory.
  proc comments_add_picture_from_memory(comments: OpeComments;
      data: cstring; size: csize_t; picture_type: cint;
          description: cstring): cint {.opeProc.}
  opeCheck:
    let desc = if description == "": (cstring) nil else: (cstring)description
    res = comments_add_picture_from_memory(
        com.ope, cast[cstring](data), (csize_t)size, (cint)kind, desc)


proc `=destroy`*(enc: var Encoder) =
  if not enc.ope.pointer.isNil:
    proc encoder_destroy(enc: OpeEncoder) {.opeProc.}
    encoder_destroy(enc.ope)
    reset enc.ope

proc initFileEncoder*(path: string; com: Comments; rate: int32; channels: cint;
    family = monoStereo): Encoder =
  ## Create a new OggOpus file.
  proc encoder_create_file(path: cstring; comments: OpeComments;
                               rate: int32; channels: cint; family: cint;
                               error: ptr cint): OpeEncoder {.opeProc.}
  opeCheck:
    result.ope = encoder_create_file(
      path, com.ope, (int32)rate, channels, (cint)family, err)


proc initCallbackEncoder*(callbacks: Callbacks; userData: pointer;
    comments: Comments; rate: int32; channels: cint;
    family = monoStereo): Encoder =
  ## Create a new OggOpus stream to be handled using callbacks
  proc encoder_create_callbacks(callbacks: ptr OpusEncCallbacks;
                                    user_data: pointer;
                                    comments: OpeComments; rate: int32;
                                    channels: cint; family: cint;
                                        error: ptr cint): OpeEncoder {.opeProc.}
  let
    callbacks = OpusEncCallbacks(
      write: proc (user: pointer; buf: ptr cuchar; len: int32): cint {.cdecl.} =
      (cint)callbacks.write(userData, buf, (int)len)
    , close: proc (userData: pointer): cint {.cdecl.} =
      (cint)callbacks.close(userData)
    )
  opeCheck:
    result.ope = encoder_create_callbacks(unsafeAddr callbacks, userData,
        comments.ope, rate, channels, (cint)family, err)


proc initPullEncoder*(com: Comments; rate: int32; channels: cint;
    family = monoStereo): Encoder =
  ## Create a new OggOpus stream to be used along with ``getPage``
  ## This is mostly useful for muxing with other streams.
  proc encoder_create_pull(comments: OpeComments; rate: int32;
                               channels: cint; family: cint;
                                   error: ptr cint): OpeEncoder {.opeProc.}
  opeCheck:
    result.ope = encoder_create_pull(com.ope, rate, channels, (cint)family, err)

proc deferredInitWithMapping*(enc: Encoder; family: Family; streams: cint;
    coupledStreams: cint; mapping: ptr cuchar) =
  ## Deferred initialization of the encoder to force an explicit channel
  ## mapping. This can be used to override the default channel
  ## coupling, but using it for regular surround will almost certainly
  ## lead to worse quality.
  proc encoder_deferred_init_with_mapping(enc: OpeEncoder; family: cint;
      streams: cint; coupled: cint; mapping: ptr cuchar): cint {.opeProc.}
  opeCheck:
    res = encoder_deferred_init_with_mapping(enc.ope, (cint)family, streams,
        coupledStreams, mapping)

proc writeFloat*(enc: Encoder; pcm: ptr cfloat;
                             samplesPerChannel: int) =
  ## Add/encode any number of float samples to the stream.
  proc encoder_write_float(enc: OpeEncoder; pcm: ptr cfloat;
                               samples_per_channel: cint): cint {.opeProc.}
  opeCheck:
    res = encoder_write_float(enc.ope, pcm, (cint)samplesPerChannel)

proc write*(enc: Encoder; pcm: ptr int16;
                       samplesPerChannel: int) =
  ## Add/encode any number of 16-bit linear samples to the stream.
  proc encoder_write(enc: OpeEncoder; pcm: ptr int16;
                         samples_per_channel: cint): cint {.opeProc.}
  opeCheck:
    res = encoder_write(enc.ope, pcm, (cint)samplesPerChannel)

type Page* = tuple
  data: pointer
  size: int

proc getPage*(enc: Encoder; flush: bool): Option[Page] =
  ## Get the next page from the stream (only if using ``encoderCreatePull``).
  proc encoder_get_page(enc: OpeEncoder; page: ptr ptr cuchar;
                            len: ptr int32; flush: cint): cint {.opeProc.}
  var
    page: ptr cuchar
    len: int32
  let res = encoder_get_page(enc.ope, addr page, addr len, (cint)flush)
  if res == 0:
    none(Page)
  else:
    some((data: (pointer)page, size: (int)len))

proc drain*(enc: Encoder) =
  ## Finalizes the stream.
  proc encoder_drain(enc: OpeEncoder): cint {.opeProc.}
  opeCheck:
    res = encoder_drain(enc.ope)

proc chainCurrent*(enc: Encoder; comments: Comments) =
  ## Ends the stream and create a new stream within the same file.
  proc encoder_chain_current(enc: OpeEncoder;
      comments: OpeComments): cint {.opeProc.}
  opeCheck:
    res = encoder_chain_current(enc.ope, comments.ope)

proc continueNewFile*(enc: Encoder; path: string;
                                   comments: Comments) =
  ## Ends the stream and create a new file.
  proc encoder_continue_new_file(enc: OpeEncoder; path: cstring;
                                     com: OpeComments): cint {.opeProc.}
  opeCheck:
    res = encoder_continue_new_file(enc.ope, path, comments.ope)

proc continueNewCallbacks*(enc: Encoder; userData: pointer;
                                        comments: Comments) =
  ## Ends the stream and create a new file (callback-based).
  proc encoder_continue_new_callbacks(enc: OpeEncoder; user_data: pointer;
      comments: OpeComments): cint {.opeProc.}
  opeCheck:
    res = encoder_continue_new_callbacks(enc.ope, userData, comments.ope)

proc flushHeader*(enc: Encoder) =
  ## Write out the header now rather than wait for audio to begin.
  proc encoder_flush_header(enc: OpeEncoder): cint {.opeProc.}
  opeCheck:
    res = encoder_flush_header(enc.ope)