node-av-lgpl 0.0.0 → 7.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +711 -0
  3. package/dist/api/audio-frame-buffer.d.ts +636 -0
  4. package/dist/api/audio-frame-buffer.js +1027 -0
  5. package/dist/api/audio-frame-buffer.js.map +1 -0
  6. package/dist/api/bitstream-filter.d.ts +938 -0
  7. package/dist/api/bitstream-filter.js +1427 -0
  8. package/dist/api/bitstream-filter.js.map +1 -0
  9. package/dist/api/constants.d.ts +44 -0
  10. package/dist/api/constants.js +45 -0
  11. package/dist/api/constants.js.map +1 -0
  12. package/dist/api/data/test_av1.ivf +0 -0
  13. package/dist/api/data/test_h264.h264 +0 -0
  14. package/dist/api/data/test_hevc.h265 +0 -0
  15. package/dist/api/data/test_mjpeg.mjpeg +0 -0
  16. package/dist/api/data/test_vp8.ivf +0 -0
  17. package/dist/api/data/test_vp9.ivf +0 -0
  18. package/dist/api/decoder.d.ts +1413 -0
  19. package/dist/api/decoder.js +2367 -0
  20. package/dist/api/decoder.js.map +1 -0
  21. package/dist/api/demuxer.d.ts +1295 -0
  22. package/dist/api/demuxer.js +2344 -0
  23. package/dist/api/demuxer.js.map +1 -0
  24. package/dist/api/device.d.ts +589 -0
  25. package/dist/api/device.js +964 -0
  26. package/dist/api/device.js.map +1 -0
  27. package/dist/api/encoder-pool.d.ts +262 -0
  28. package/dist/api/encoder-pool.js +397 -0
  29. package/dist/api/encoder-pool.js.map +1 -0
  30. package/dist/api/encoder.d.ts +1509 -0
  31. package/dist/api/encoder.js +2619 -0
  32. package/dist/api/encoder.js.map +1 -0
  33. package/dist/api/filter-complex.d.ts +828 -0
  34. package/dist/api/filter-complex.js +1661 -0
  35. package/dist/api/filter-complex.js.map +1 -0
  36. package/dist/api/filter-presets.d.ts +800 -0
  37. package/dist/api/filter-presets.js +1554 -0
  38. package/dist/api/filter-presets.js.map +1 -0
  39. package/dist/api/filter.d.ts +1339 -0
  40. package/dist/api/filter.js +2075 -0
  41. package/dist/api/filter.js.map +1 -0
  42. package/dist/api/fmp4-stream.d.ts +797 -0
  43. package/dist/api/fmp4-stream.js +1428 -0
  44. package/dist/api/fmp4-stream.js.map +1 -0
  45. package/dist/api/hardware.d.ts +677 -0
  46. package/dist/api/hardware.js +1300 -0
  47. package/dist/api/hardware.js.map +1 -0
  48. package/dist/api/index.d.ts +21 -0
  49. package/dist/api/index.js +38 -0
  50. package/dist/api/index.js.map +1 -0
  51. package/dist/api/io-stream.d.ts +308 -0
  52. package/dist/api/io-stream.js +358 -0
  53. package/dist/api/io-stream.js.map +1 -0
  54. package/dist/api/muxer.d.ts +1311 -0
  55. package/dist/api/muxer.js +2672 -0
  56. package/dist/api/muxer.js.map +1 -0
  57. package/dist/api/pipeline.d.ts +801 -0
  58. package/dist/api/pipeline.js +1344 -0
  59. package/dist/api/pipeline.js.map +1 -0
  60. package/dist/api/probe.d.ts +128 -0
  61. package/dist/api/probe.js +227 -0
  62. package/dist/api/probe.js.map +1 -0
  63. package/dist/api/rtp-stream.d.ts +497 -0
  64. package/dist/api/rtp-stream.js +1131 -0
  65. package/dist/api/rtp-stream.js.map +1 -0
  66. package/dist/api/scaler.d.ts +561 -0
  67. package/dist/api/scaler.js +918 -0
  68. package/dist/api/scaler.js.map +1 -0
  69. package/dist/api/types.d.ts +17 -0
  70. package/dist/api/types.js +2 -0
  71. package/dist/api/types.js.map +1 -0
  72. package/dist/api/utilities/async-queue.d.ts +149 -0
  73. package/dist/api/utilities/async-queue.js +254 -0
  74. package/dist/api/utilities/async-queue.js.map +1 -0
  75. package/dist/api/utilities/audio-sample.d.ts +117 -0
  76. package/dist/api/utilities/audio-sample.js +112 -0
  77. package/dist/api/utilities/audio-sample.js.map +1 -0
  78. package/dist/api/utilities/audio-transcode.d.ts +20 -0
  79. package/dist/api/utilities/audio-transcode.js +27 -0
  80. package/dist/api/utilities/audio-transcode.js.map +1 -0
  81. package/dist/api/utilities/channel-layout.d.ts +76 -0
  82. package/dist/api/utilities/channel-layout.js +80 -0
  83. package/dist/api/utilities/channel-layout.js.map +1 -0
  84. package/dist/api/utilities/codec-format.d.ts +87 -0
  85. package/dist/api/utilities/codec-format.js +117 -0
  86. package/dist/api/utilities/codec-format.js.map +1 -0
  87. package/dist/api/utilities/context-options.d.ts +25 -0
  88. package/dist/api/utilities/context-options.js +23 -0
  89. package/dist/api/utilities/context-options.js.map +1 -0
  90. package/dist/api/utilities/electron-shared-texture.d.ts +368 -0
  91. package/dist/api/utilities/electron-shared-texture.js +540 -0
  92. package/dist/api/utilities/electron-shared-texture.js.map +1 -0
  93. package/dist/api/utilities/image.d.ts +207 -0
  94. package/dist/api/utilities/image.js +213 -0
  95. package/dist/api/utilities/image.js.map +1 -0
  96. package/dist/api/utilities/index.d.ts +14 -0
  97. package/dist/api/utilities/index.js +27 -0
  98. package/dist/api/utilities/index.js.map +1 -0
  99. package/dist/api/utilities/live-decoding.d.ts +15 -0
  100. package/dist/api/utilities/live-decoding.js +32 -0
  101. package/dist/api/utilities/live-decoding.js.map +1 -0
  102. package/dist/api/utilities/media-type.d.ts +49 -0
  103. package/dist/api/utilities/media-type.js +53 -0
  104. package/dist/api/utilities/media-type.js.map +1 -0
  105. package/dist/api/utilities/packet-age-watchdog.d.ts +198 -0
  106. package/dist/api/utilities/packet-age-watchdog.js +594 -0
  107. package/dist/api/utilities/packet-age-watchdog.js.map +1 -0
  108. package/dist/api/utilities/pixel-format.d.ts +89 -0
  109. package/dist/api/utilities/pixel-format.js +97 -0
  110. package/dist/api/utilities/pixel-format.js.map +1 -0
  111. package/dist/api/utilities/sample-format.d.ts +129 -0
  112. package/dist/api/utilities/sample-format.js +141 -0
  113. package/dist/api/utilities/sample-format.js.map +1 -0
  114. package/dist/api/utilities/scale-size.d.ts +51 -0
  115. package/dist/api/utilities/scale-size.js +55 -0
  116. package/dist/api/utilities/scale-size.js.map +1 -0
  117. package/dist/api/utilities/scheduler.d.ts +138 -0
  118. package/dist/api/utilities/scheduler.js +98 -0
  119. package/dist/api/utilities/scheduler.js.map +1 -0
  120. package/dist/api/utilities/streaming.d.ts +186 -0
  121. package/dist/api/utilities/streaming.js +309 -0
  122. package/dist/api/utilities/streaming.js.map +1 -0
  123. package/dist/api/utilities/timestamp.d.ts +193 -0
  124. package/dist/api/utilities/timestamp.js +206 -0
  125. package/dist/api/utilities/timestamp.js.map +1 -0
  126. package/dist/api/utilities/video-transcode.d.ts +218 -0
  127. package/dist/api/utilities/video-transcode.js +624 -0
  128. package/dist/api/utilities/video-transcode.js.map +1 -0
  129. package/dist/api/utilities/whisper-model.d.ts +330 -0
  130. package/dist/api/utilities/whisper-model.js +587 -0
  131. package/dist/api/utilities/whisper-model.js.map +1 -0
  132. package/dist/api/utils.d.ts +19 -0
  133. package/dist/api/utils.js +39 -0
  134. package/dist/api/utils.js.map +1 -0
  135. package/dist/api/webrtc-stream.d.ts +294 -0
  136. package/dist/api/webrtc-stream.js +546 -0
  137. package/dist/api/webrtc-stream.js.map +1 -0
  138. package/dist/api/whisper.d.ts +326 -0
  139. package/dist/api/whisper.js +402 -0
  140. package/dist/api/whisper.js.map +1 -0
  141. package/dist/constants/bsf-options.d.ts +364 -0
  142. package/dist/constants/bsf-options.js +7 -0
  143. package/dist/constants/bsf-options.js.map +1 -0
  144. package/dist/constants/channel-layouts.d.ts +53 -0
  145. package/dist/constants/channel-layouts.js +57 -0
  146. package/dist/constants/channel-layouts.js.map +1 -0
  147. package/dist/constants/constants.d.ts +2435 -0
  148. package/dist/constants/constants.js +1989 -0
  149. package/dist/constants/constants.js.map +1 -0
  150. package/dist/constants/decoders.d.ts +651 -0
  151. package/dist/constants/decoders.js +641 -0
  152. package/dist/constants/decoders.js.map +1 -0
  153. package/dist/constants/encoders.d.ts +314 -0
  154. package/dist/constants/encoders.js +309 -0
  155. package/dist/constants/encoders.js.map +1 -0
  156. package/dist/constants/filter-options.d.ts +12099 -0
  157. package/dist/constants/filter-options.js +7 -0
  158. package/dist/constants/filter-options.js.map +1 -0
  159. package/dist/constants/format-options.d.ts +4319 -0
  160. package/dist/constants/format-options.js +7 -0
  161. package/dist/constants/format-options.js.map +1 -0
  162. package/dist/constants/formats.d.ts +18 -0
  163. package/dist/constants/formats.js +7 -0
  164. package/dist/constants/formats.js.map +1 -0
  165. package/dist/constants/hardware.d.ts +26 -0
  166. package/dist/constants/hardware.js +27 -0
  167. package/dist/constants/hardware.js.map +1 -0
  168. package/dist/constants/index.d.ts +10 -0
  169. package/dist/constants/index.js +6 -0
  170. package/dist/constants/index.js.map +1 -0
  171. package/dist/constants/options.d.ts +6217 -0
  172. package/dist/constants/options.js +7 -0
  173. package/dist/constants/options.js.map +1 -0
  174. package/dist/ffmpeg/index.d.ts +101 -0
  175. package/dist/ffmpeg/index.js +161 -0
  176. package/dist/ffmpeg/index.js.map +1 -0
  177. package/dist/ffmpeg/version.d.ts +6 -0
  178. package/dist/ffmpeg/version.js +7 -0
  179. package/dist/ffmpeg/version.js.map +1 -0
  180. package/dist/index.d.ts +4 -0
  181. package/dist/index.js +9 -0
  182. package/dist/index.js.map +1 -0
  183. package/dist/lib/audio-fifo.d.ts +399 -0
  184. package/dist/lib/audio-fifo.js +431 -0
  185. package/dist/lib/audio-fifo.js.map +1 -0
  186. package/dist/lib/binding.d.ts +242 -0
  187. package/dist/lib/binding.js +102 -0
  188. package/dist/lib/binding.js.map +1 -0
  189. package/dist/lib/bitstream-filter-context.d.ts +381 -0
  190. package/dist/lib/bitstream-filter-context.js +443 -0
  191. package/dist/lib/bitstream-filter-context.js.map +1 -0
  192. package/dist/lib/bitstream-filter.d.ts +140 -0
  193. package/dist/lib/bitstream-filter.js +154 -0
  194. package/dist/lib/bitstream-filter.js.map +1 -0
  195. package/dist/lib/codec-context.d.ts +1069 -0
  196. package/dist/lib/codec-context.js +1336 -0
  197. package/dist/lib/codec-context.js.map +1 -0
  198. package/dist/lib/codec-parameters.d.ts +664 -0
  199. package/dist/lib/codec-parameters.js +813 -0
  200. package/dist/lib/codec-parameters.js.map +1 -0
  201. package/dist/lib/codec-parser.d.ts +201 -0
  202. package/dist/lib/codec-parser.js +213 -0
  203. package/dist/lib/codec-parser.js.map +1 -0
  204. package/dist/lib/codec.d.ts +617 -0
  205. package/dist/lib/codec.js +746 -0
  206. package/dist/lib/codec.js.map +1 -0
  207. package/dist/lib/device.d.ts +294 -0
  208. package/dist/lib/device.js +326 -0
  209. package/dist/lib/device.js.map +1 -0
  210. package/dist/lib/dictionary.d.ts +333 -0
  211. package/dist/lib/dictionary.js +372 -0
  212. package/dist/lib/dictionary.js.map +1 -0
  213. package/dist/lib/error.d.ts +311 -0
  214. package/dist/lib/error.js +395 -0
  215. package/dist/lib/error.js.map +1 -0
  216. package/dist/lib/fifo.d.ts +416 -0
  217. package/dist/lib/fifo.js +453 -0
  218. package/dist/lib/fifo.js.map +1 -0
  219. package/dist/lib/filter-context.d.ts +714 -0
  220. package/dist/lib/filter-context.js +783 -0
  221. package/dist/lib/filter-context.js.map +1 -0
  222. package/dist/lib/filter-graph-segment.d.ts +161 -0
  223. package/dist/lib/filter-graph-segment.js +172 -0
  224. package/dist/lib/filter-graph-segment.js.map +1 -0
  225. package/dist/lib/filter-graph.d.ts +641 -0
  226. package/dist/lib/filter-graph.js +704 -0
  227. package/dist/lib/filter-graph.js.map +1 -0
  228. package/dist/lib/filter-inout.d.ts +202 -0
  229. package/dist/lib/filter-inout.js +261 -0
  230. package/dist/lib/filter-inout.js.map +1 -0
  231. package/dist/lib/filter.d.ts +243 -0
  232. package/dist/lib/filter.js +272 -0
  233. package/dist/lib/filter.js.map +1 -0
  234. package/dist/lib/format-context.d.ts +1335 -0
  235. package/dist/lib/format-context.js +1455 -0
  236. package/dist/lib/format-context.js.map +1 -0
  237. package/dist/lib/frame-utils.d.ts +116 -0
  238. package/dist/lib/frame-utils.js +98 -0
  239. package/dist/lib/frame-utils.js.map +1 -0
  240. package/dist/lib/frame.d.ts +1315 -0
  241. package/dist/lib/frame.js +1535 -0
  242. package/dist/lib/frame.js.map +1 -0
  243. package/dist/lib/hardware-device-context.d.ts +362 -0
  244. package/dist/lib/hardware-device-context.js +383 -0
  245. package/dist/lib/hardware-device-context.js.map +1 -0
  246. package/dist/lib/hardware-frames-context.d.ts +418 -0
  247. package/dist/lib/hardware-frames-context.js +471 -0
  248. package/dist/lib/hardware-frames-context.js.map +1 -0
  249. package/dist/lib/index.d.ts +35 -0
  250. package/dist/lib/index.js +60 -0
  251. package/dist/lib/index.js.map +1 -0
  252. package/dist/lib/input-format.d.ts +249 -0
  253. package/dist/lib/input-format.js +306 -0
  254. package/dist/lib/input-format.js.map +1 -0
  255. package/dist/lib/io-context.d.ts +698 -0
  256. package/dist/lib/io-context.js +771 -0
  257. package/dist/lib/io-context.js.map +1 -0
  258. package/dist/lib/log.d.ts +174 -0
  259. package/dist/lib/log.js +184 -0
  260. package/dist/lib/log.js.map +1 -0
  261. package/dist/lib/native-types.d.ts +1024 -0
  262. package/dist/lib/native-types.js +2 -0
  263. package/dist/lib/native-types.js.map +1 -0
  264. package/dist/lib/option.d.ts +953 -0
  265. package/dist/lib/option.js +1607 -0
  266. package/dist/lib/option.js.map +1 -0
  267. package/dist/lib/output-format.d.ts +222 -0
  268. package/dist/lib/output-format.js +259 -0
  269. package/dist/lib/output-format.js.map +1 -0
  270. package/dist/lib/packet.d.ts +521 -0
  271. package/dist/lib/packet.js +613 -0
  272. package/dist/lib/packet.js.map +1 -0
  273. package/dist/lib/rational.d.ts +251 -0
  274. package/dist/lib/rational.js +278 -0
  275. package/dist/lib/rational.js.map +1 -0
  276. package/dist/lib/software-resample-context.d.ts +552 -0
  277. package/dist/lib/software-resample-context.js +592 -0
  278. package/dist/lib/software-resample-context.js.map +1 -0
  279. package/dist/lib/software-scale-context.d.ts +344 -0
  280. package/dist/lib/software-scale-context.js +366 -0
  281. package/dist/lib/software-scale-context.js.map +1 -0
  282. package/dist/lib/stream.d.ts +383 -0
  283. package/dist/lib/stream.js +524 -0
  284. package/dist/lib/stream.js.map +1 -0
  285. package/dist/lib/sync-queue.d.ts +179 -0
  286. package/dist/lib/sync-queue.js +197 -0
  287. package/dist/lib/sync-queue.js.map +1 -0
  288. package/dist/lib/types.d.ts +34 -0
  289. package/dist/lib/types.js +2 -0
  290. package/dist/lib/types.js.map +1 -0
  291. package/dist/lib/utilities.d.ts +1198 -0
  292. package/dist/lib/utilities.js +1302 -0
  293. package/dist/lib/utilities.js.map +1 -0
  294. package/dist/utils/electron.d.ts +49 -0
  295. package/dist/utils/electron.js +63 -0
  296. package/dist/utils/electron.js.map +1 -0
  297. package/dist/utils/index.d.ts +6 -0
  298. package/dist/utils/index.js +7 -0
  299. package/dist/utils/index.js.map +1 -0
  300. package/dist/utils/libc.d.ts +11 -0
  301. package/dist/utils/libc.js +35 -0
  302. package/dist/utils/libc.js.map +1 -0
  303. package/dist/utils/variant.d.ts +26 -0
  304. package/dist/utils/variant.js +31 -0
  305. package/dist/utils/variant.js.map +1 -0
  306. package/dist/webrtc/index.d.ts +4 -0
  307. package/dist/webrtc/index.js +7 -0
  308. package/dist/webrtc/index.js.map +1 -0
  309. package/install/check.js +124 -0
  310. package/install/ffmpeg.js +66 -0
  311. package/package.json +165 -3
@@ -0,0 +1,2619 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ /* eslint-disable @stylistic/indent-binary-ops */
54
+ import { AV_CHANNEL_ORDER_UNSPEC, AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, AV_CODEC_CAP_PARAM_CHANGE, AV_CODEC_FLAG_COPY_OPAQUE, AV_CODEC_FLAG_FRAME_DURATION, AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AV_LOG_WARNING, AV_NOPTS_VALUE, AV_PICTURE_TYPE_I, AV_PICTURE_TYPE_NONE, AV_PIX_FMT_NONE, AV_PKT_FLAG_TRUSTED, AV_ROUND_NEAR_INF, AVCHROMA_LOC_UNSPECIFIED, AVERROR_EAGAIN, AVERROR_ENCODER_NOT_FOUND, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, EOF, SWS_BILINEAR, } from '../constants/constants.js';
55
+ import { CodecContext } from '../lib/codec-context.js';
56
+ import { Codec } from '../lib/codec.js';
57
+ import { Dictionary } from '../lib/dictionary.js';
58
+ import { FFmpegError } from '../lib/error.js';
59
+ import { Frame } from '../lib/frame.js';
60
+ import { Log } from '../lib/log.js';
61
+ import { Packet } from '../lib/packet.js';
62
+ import { Rational } from '../lib/rational.js';
63
+ import { SoftwareResampleContext } from '../lib/software-resample-context.js';
64
+ import { SoftwareScaleContext } from '../lib/software-scale-context.js';
65
+ import { avChannelLayoutDefault, avGetPixFmtName, avGetSampleFmtName, avRescaleQ, avRescaleRnd } from '../lib/utilities.js';
66
+ import { assertAudioFrameBufferOptions, AudioFrameBuffer } from './audio-frame-buffer.js';
67
+ import { FRAME_THREAD_QUEUE_SIZE, PACKET_THREAD_QUEUE_SIZE } from './constants.js';
68
+ import { AsyncQueue } from './utilities/async-queue.js';
69
+ import { pickSupportedLayout, pickSupportedPixelFormat, pickSupportedRate, pickSupportedSampleFormat } from './utilities/codec-format.js';
70
+ import { applyContextOptions } from './utilities/context-options.js';
71
+ import { SchedulerControl } from './utilities/scheduler.js';
72
+ import { parseBitrate } from './utils.js';
73
+ /**
74
+ * Rate-control fields that accept a unit-suffixed string and need normalizing.
75
+ *
76
+ * @internal
77
+ */
78
+ const BITRATE_FIELDS = ['bitRate', 'rcMinRate', 'rcMaxRate', 'rcBufferSize'];
79
+ /**
80
+ * Normalize a {@link BitrateValue} to a bigint of bits per second.
81
+ *
82
+ * @param value - The number/bigint/`'5M'`-style value
83
+ *
84
+ * @returns The value in bits per second
85
+ *
86
+ * @internal
87
+ */
88
+ function normalizeBitrate(value) {
89
+ return typeof value === 'string' ? parseBitrate(value) : BigInt(value);
90
+ }
91
+ /**
92
+ * Highest rate taken as a frame rate, above it a rate is a stream clock.
93
+ *
94
+ * @internal
95
+ */
96
+ const MAX_FRAME_RATE = 240;
97
+ /**
98
+ * Frame rate declared when nothing knows the rate, FFmpeg CLI's default.
99
+ *
100
+ * @internal
101
+ */
102
+ const FALLBACK_FRAME_RATE = 25;
103
+ /**
104
+ * Whether a rate is a plausible frame rate rather than a stream clock.
105
+ *
106
+ * @param rate - The rate
107
+ *
108
+ * @returns True for a positive rate up to {@link MAX_FRAME_RATE}
109
+ *
110
+ * @internal
111
+ */
112
+ function isFrameRate(rate) {
113
+ return rate.num > 0 && rate.den > 0 && rate.num / rate.den <= MAX_FRAME_RATE;
114
+ }
115
+ /**
116
+ * High-level encoder for audio and video streams.
117
+ *
118
+ * Provides a simplified interface for encoding media frames to packets.
119
+ * Handles codec initialization, hardware acceleration setup, and packet management.
120
+ * Supports both synchronous frame-by-frame encoding and async iteration over packets.
121
+ * Essential component in media processing pipelines for converting raw frames to compressed data.
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * import { Encoder } from 'node-av/api';
126
+ * import { AV_CODEC_ID_H264, FF_ENCODER_LIBX264 } from 'node-av/constants';
127
+ *
128
+ * // Create H.264 encoder
129
+ * const encoder = await Encoder.create(FF_ENCODER_LIBX264, {
130
+ * type: 'video',
131
+ * width: 1920,
132
+ * height: 1080,
133
+ * pixelFormat: AV_PIX_FMT_YUV420P,
134
+ * timeBase: { num: 1, den: 30 },
135
+ * frameRate: { num: 30, den: 1 }
136
+ * }, {
137
+ * bitrate: '5M',
138
+ * gopSize: 60
139
+ * });
140
+ *
141
+ * // Encode frames
142
+ * const packet = await encoder.encode(frame);
143
+ * if (packet) {
144
+ * await output.writePacket(packet);
145
+ * packet.free();
146
+ * }
147
+ * ```
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * // Hardware-accelerated encoding with lazy initialization
152
+ * import { HardwareContext } from 'node-av/api';
153
+ * import { FF_ENCODER_H264_VIDEOTOOLBOX } from 'node-av/constants';
154
+ *
155
+ * const hw = HardwareContext.auto();
156
+ * const encoderCodec = hw?.getEncoderCodec('h264') ?? FF_ENCODER_H264_VIDEOTOOLBOX;
157
+ * const encoder = await Encoder.create(encoderCodec, {
158
+ * timeBase: video.timeBase,
159
+ * bitrate: '10M'
160
+ * });
161
+ *
162
+ * // Hardware context will be detected from first frame's hw_frames_ctx
163
+ * for await (const packet of encoder.packets(frames)) {
164
+ * await output.writePacket(packet);
165
+ * packet.free();
166
+ * }
167
+ * ```
168
+ *
169
+ * @see {@link Decoder} For decoding packets to frames
170
+ * @see {@link Muxer} For writing encoded packets
171
+ * @see {@link HardwareContext} For GPU acceleration
172
+ */
173
+ export class Encoder {
174
+ codecContext;
175
+ packet;
176
+ codec;
177
+ initializePromise = null;
178
+ initialized = false;
179
+ isClosed = false;
180
+ opts;
181
+ options;
182
+ audioFrameBuffer;
183
+ autoResample;
184
+ audioResampler;
185
+ resampledFrame;
186
+ resampleChangesRate = false;
187
+ resampleInputRate = 0;
188
+ audioInputLayout;
189
+ autoFormat;
190
+ videoScaler;
191
+ scaledFrame;
192
+ videoTargetFormat;
193
+ supportsParamChange;
194
+ encoderChannels;
195
+ nextVideoPts;
196
+ droppedKeyframe = false;
197
+ keyframeRequested = false;
198
+ // Worker pattern for push-based processing
199
+ inputQueue;
200
+ outputQueue;
201
+ workerPromise = null;
202
+ pipeToPromise = null;
203
+ signal;
204
+ /**
205
+ * @param codecContext - Configured codec context
206
+ *
207
+ * @param codec - Encoder codec
208
+ *
209
+ * @param options - Encoder options
210
+ *
211
+ * @param opts - Encoder options as Dictionary
212
+ *
213
+ * @internal
214
+ */
215
+ constructor(codecContext, codec, options, opts) {
216
+ this.codecContext = codecContext;
217
+ this.codec = codec;
218
+ this.options = options;
219
+ this.opts = opts;
220
+ this.autoResample = options.autoResample ?? false;
221
+ this.autoFormat = options.autoFormat ?? false;
222
+ this.packet = new Packet();
223
+ this.packet.alloc();
224
+ this.inputQueue = new AsyncQueue(FRAME_THREAD_QUEUE_SIZE, (f) => f.free());
225
+ this.outputQueue = new AsyncQueue(PACKET_THREAD_QUEUE_SIZE, (p) => p.free());
226
+ }
227
+ /**
228
+ * Create an encoder with specified codec and options.
229
+ *
230
+ * Initializes an encoder with the appropriate codec and configuration.
231
+ * Uses lazy initialization - encoder is opened when first frame is received.
232
+ * Hardware context will be automatically detected from first frame if not provided.
233
+ *
234
+ * Direct mapping to avcodec_find_encoder_by_name() or avcodec_find_encoder().
235
+ *
236
+ * @param encoderCodec - Codec name, ID, or instance to use for encoding
237
+ *
238
+ * @param options - Optional encoder configuration options including required timeBase
239
+ *
240
+ * @returns Configured encoder instance
241
+ *
242
+ * @throws {Error} If encoder not found
243
+ *
244
+ * @throws {RangeError} If an audioFrameBuffer option is out of range
245
+ *
246
+ * @example
247
+ * ```typescript
248
+ * // From decoder stream info
249
+ * const encoder = await Encoder.create(FF_ENCODER_LIBX264, {
250
+ * timeBase: video.timeBase,
251
+ * bitrate: '5M',
252
+ * gopSize: 60,
253
+ * options: {
254
+ * preset: 'fast',
255
+ * crf: '23'
256
+ * }
257
+ * });
258
+ * ```
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * // With custom stream info
263
+ * const encoder = await Encoder.create(FF_ENCODER_AAC, {
264
+ * timeBase: audio.timeBase,
265
+ * bitrate: '192k'
266
+ * });
267
+ * ```
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * // Hardware encoder
272
+ * const hw = HardwareContext.auto();
273
+ * const encoderCodec = hw?.getEncoderCodec('h264') ?? FF_ENCODER_H264_VIDEOTOOLBOX;
274
+ * const encoder = await Encoder.create(encoderCodec, {
275
+ * timeBase: video.timeBase,
276
+ * bitrate: '8M'
277
+ * });
278
+ * ```
279
+ *
280
+ * @see {@link EncoderOptions} For configuration options
281
+ * @see {@link createSync} For synchronous version
282
+ */
283
+ static async create(encoderCodec, options = {}) {
284
+ // Copied: defaults (e.g. bitrate) are filled in below
285
+ options = { ...options };
286
+ let codec = null;
287
+ if (encoderCodec instanceof Codec) {
288
+ codec = encoderCodec;
289
+ }
290
+ else if (typeof encoderCodec === 'string') {
291
+ codec = Codec.findEncoderByName(encoderCodec);
292
+ }
293
+ else {
294
+ codec = Codec.findEncoder(encoderCodec);
295
+ }
296
+ if (!codec) {
297
+ throw new FFmpegError(AVERROR_ENCODER_NOT_FOUND);
298
+ }
299
+ if (options.audioFrameBuffer) {
300
+ assertAudioFrameBufferOptions(options.audioFrameBuffer);
301
+ }
302
+ // Allocate codec context
303
+ const codecContext = new CodecContext();
304
+ codecContext.allocContext3(codec);
305
+ // Apply encoder-specific options
306
+ if (options.gopSize !== undefined) {
307
+ codecContext.gopSize = options.gopSize;
308
+ }
309
+ if (options.maxBFrames !== undefined) {
310
+ codecContext.maxBFrames = options.maxBFrames;
311
+ }
312
+ // Audio bitrate is left to the codec's default like FFmpeg CLI does: it
313
+ // depends on codec, channels and rate (AAC 69k per mono channel, G.726 32k),
314
+ // and some codecs only open at specific rates (G.723.1: 6.3k/5.3k)
315
+ if (options.bitrate === undefined && codec.type !== AVMEDIA_TYPE_AUDIO) {
316
+ options.bitrate = 1_000_000;
317
+ }
318
+ if (options.bitrate !== undefined) {
319
+ codecContext.bitRate = typeof options.bitrate === 'string' ? parseBitrate(options.bitrate) : BigInt(options.bitrate);
320
+ }
321
+ if (options.minRate !== undefined) {
322
+ const minRate = typeof options.minRate === 'string' ? parseBitrate(options.minRate) : BigInt(options.minRate);
323
+ codecContext.rcMinRate = minRate;
324
+ }
325
+ if (options.maxRate !== undefined) {
326
+ const maxRate = typeof options.maxRate === 'string' ? parseBitrate(options.maxRate) : BigInt(options.maxRate);
327
+ codecContext.rcMaxRate = maxRate;
328
+ }
329
+ if (options.bufSize !== undefined) {
330
+ const bufSize = typeof options.bufSize === 'string' ? parseBitrate(options.bufSize) : BigInt(options.bufSize);
331
+ codecContext.rcBufferSize = Number(bufSize);
332
+ }
333
+ // Thread parameters need to be set before open
334
+ codecContext.threadCount = options.threadCount ?? 0;
335
+ if (options.threadType !== undefined) {
336
+ codecContext.threadType = options.threadType;
337
+ }
338
+ // Loose view for internal use: the public signature narrows `options` to the
339
+ // codec, but internally codec options are handled as a generic dictionary.
340
+ const looseOptions = options;
341
+ const opts = looseOptions.options ? Dictionary.fromObject(looseOptions.options) : undefined;
342
+ const encoder = new Encoder(codecContext, codec, looseOptions, opts);
343
+ if (options.signal) {
344
+ options.signal.throwIfAborted();
345
+ encoder.signal = options.signal;
346
+ }
347
+ return encoder;
348
+ }
349
+ /**
350
+ * Create an encoder with specified codec and options synchronously.
351
+ * Synchronous version of create.
352
+ *
353
+ * Initializes an encoder with the appropriate codec and configuration.
354
+ * Uses lazy initialization - encoder is opened when first frame is received.
355
+ * Hardware context will be automatically detected from first frame if not provided.
356
+ *
357
+ * Direct mapping to avcodec_find_encoder_by_name() or avcodec_find_encoder().
358
+ *
359
+ * @param encoderCodec - Codec name, ID, or instance to use for encoding
360
+ *
361
+ * @param options - Optional encoder configuration options including required timeBase
362
+ *
363
+ * @returns Configured encoder instance
364
+ *
365
+ * @throws {Error} If encoder not found or timeBase not provided
366
+ *
367
+ * @throws {FFmpegError} If codec allocation fails
368
+ *
369
+ * @throws {RangeError} If an audioFrameBuffer option is out of range
370
+ *
371
+ * @example
372
+ * ```typescript
373
+ * // From decoder stream info
374
+ * const encoder = await Encoder.create(FF_ENCODER_LIBX264, {
375
+ * timeBase: video.timeBase,
376
+ * bitrate: '5M',
377
+ * gopSize: 60,
378
+ * options: {
379
+ * preset: 'fast',
380
+ * crf: '23'
381
+ * }
382
+ * });
383
+ * ```
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * // With custom stream info
388
+ * const encoder = await Encoder.create(FF_ENCODER_AAC, {
389
+ * timeBase: audio.timeBase,
390
+ * bitrate: '192k'
391
+ * });
392
+ * ```
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * // Hardware encoder
397
+ * const hw = HardwareContext.auto();
398
+ * const encoderCodec = hw?.getEncoderCodec('h264') ?? FF_ENCODER_H264_VIDEOTOOLBOX;
399
+ * const encoder = await Encoder.create(encoderCodec, {
400
+ * timeBase: video.timeBase,
401
+ * bitrate: '8M'
402
+ * });
403
+ * ```
404
+ *
405
+ * @see {@link EncoderOptions} For configuration options
406
+ * @see {@link create} For async version
407
+ */
408
+ static createSync(encoderCodec, options = {}) {
409
+ // Copied: defaults (e.g. bitrate) are filled in below
410
+ options = { ...options };
411
+ let codec = null;
412
+ if (encoderCodec instanceof Codec) {
413
+ codec = encoderCodec;
414
+ }
415
+ else if (typeof encoderCodec === 'string') {
416
+ codec = Codec.findEncoderByName(encoderCodec);
417
+ }
418
+ else {
419
+ codec = Codec.findEncoder(encoderCodec);
420
+ }
421
+ if (!codec) {
422
+ throw new FFmpegError(AVERROR_ENCODER_NOT_FOUND);
423
+ }
424
+ if (options.audioFrameBuffer) {
425
+ assertAudioFrameBufferOptions(options.audioFrameBuffer);
426
+ }
427
+ // Allocate codec context
428
+ const codecContext = new CodecContext();
429
+ codecContext.allocContext3(codec);
430
+ if (options.bitrate === undefined && codec.type !== AVMEDIA_TYPE_AUDIO) {
431
+ options.bitrate = 1_000_000;
432
+ }
433
+ if (options.bitrate !== undefined) {
434
+ codecContext.bitRate = typeof options.bitrate === 'string' ? parseBitrate(options.bitrate) : BigInt(options.bitrate);
435
+ }
436
+ if (options.gopSize !== undefined) {
437
+ codecContext.gopSize = options.gopSize;
438
+ }
439
+ if (options.maxBFrames !== undefined) {
440
+ codecContext.maxBFrames = options.maxBFrames;
441
+ }
442
+ if (options.minRate !== undefined) {
443
+ const minRate = typeof options.minRate === 'string' ? parseBitrate(options.minRate) : BigInt(options.minRate);
444
+ codecContext.rcMinRate = minRate;
445
+ }
446
+ if (options.maxRate !== undefined) {
447
+ const maxRate = typeof options.maxRate === 'string' ? parseBitrate(options.maxRate) : BigInt(options.maxRate);
448
+ codecContext.rcMaxRate = maxRate;
449
+ }
450
+ if (options.bufSize !== undefined) {
451
+ const bufSize = typeof options.bufSize === 'string' ? parseBitrate(options.bufSize) : BigInt(options.bufSize);
452
+ codecContext.rcBufferSize = Number(bufSize);
453
+ }
454
+ // Thread parameters need to be set before open
455
+ codecContext.threadCount = options.threadCount ?? 0;
456
+ if (options.threadType !== undefined) {
457
+ codecContext.threadType = options.threadType;
458
+ }
459
+ // Loose view for internal use: the public signature narrows `options` to the
460
+ // codec, but internally codec options are handled as a generic dictionary.
461
+ const looseOptions = options;
462
+ const opts = looseOptions.options ? Dictionary.fromObject(looseOptions.options) : undefined;
463
+ const encoder = new Encoder(codecContext, codec, looseOptions, opts);
464
+ if (options.signal) {
465
+ options.signal.throwIfAborted();
466
+ encoder.signal = options.signal;
467
+ }
468
+ return encoder;
469
+ }
470
+ /**
471
+ * Encode a single frame into a self-contained image buffer.
472
+ *
473
+ * One-shot, stateless helper for intra-only image codecs (MJPEG, PNG, WebP, ...).
474
+ * Creates a fresh encoder, encodes the frame, flushes and frees everything in one call.
475
+ * The encoder adopts dimensions, pixel format and hardware context from the frame,
476
+ * so any frame size works without reconfiguration.
477
+ *
478
+ * @param encoderCodec - Encoder codec (name, ID, branded constant, or Codec)
479
+ *
480
+ * @param frame - Frame to encode
481
+ *
482
+ * @param options - Optional encoder configuration (e.g. `{ options: { q: 3 } }` for MJPEG quality)
483
+ *
484
+ * @returns Encoded image bytes
485
+ *
486
+ * @throws {FFmpegError} If the encoder is not found or encoding fails
487
+ *
488
+ * @throws {Error} If the encoder produced no output
489
+ *
490
+ * @example
491
+ * ```typescript
492
+ * const jpeg = await Encoder.encodeOne(FF_ENCODER_MJPEG, frame, { options: { q: 3 } });
493
+ * ```
494
+ *
495
+ * @see {@link EncoderPool} For reusing encoders across recurring resolutions
496
+ */
497
+ static async encodeOne(encoderCodec, frame, options = {}) {
498
+ const env_1 = { stack: [], error: void 0, hasError: false };
499
+ try {
500
+ const encoder = __addDisposableResource(env_1, await Encoder.create(encoderCodec, options), false);
501
+ const packets = [...(await encoder.encodeAll(frame)), ...(await encoder.encodeAll(null))];
502
+ try {
503
+ const data = packets[0]?.data;
504
+ if (!data) {
505
+ throw new Error(`Encoder '${encoder.getCodec().name}' produced no output for frame`);
506
+ }
507
+ return data;
508
+ }
509
+ finally {
510
+ for (const packet of packets) {
511
+ packet.free();
512
+ }
513
+ }
514
+ }
515
+ catch (e_1) {
516
+ env_1.error = e_1;
517
+ env_1.hasError = true;
518
+ }
519
+ finally {
520
+ __disposeResources(env_1);
521
+ }
522
+ }
523
+ /**
524
+ * Encode a single frame into a self-contained image buffer synchronously.
525
+ * Synchronous version of encodeOne.
526
+ *
527
+ * One-shot, stateless helper for intra-only image codecs (MJPEG, PNG, WebP, ...).
528
+ * Creates a fresh encoder, encodes the frame, flushes and frees everything in one call.
529
+ * The encoder adopts dimensions, pixel format and hardware context from the frame,
530
+ * so any frame size works without reconfiguration.
531
+ *
532
+ * @param encoderCodec - Encoder codec (name, ID, branded constant, or Codec)
533
+ *
534
+ * @param frame - Frame to encode
535
+ *
536
+ * @param options - Optional encoder configuration (e.g. `{ options: { q: 3 } }` for MJPEG quality)
537
+ *
538
+ * @returns Encoded image bytes
539
+ *
540
+ * @throws {FFmpegError} If the encoder is not found or encoding fails
541
+ *
542
+ * @throws {Error} If the encoder produced no output
543
+ *
544
+ * @example
545
+ * ```typescript
546
+ * const jpeg = Encoder.encodeOneSync(FF_ENCODER_MJPEG, frame, { options: { q: 3 } });
547
+ * ```
548
+ *
549
+ * @see {@link encodeOne} For async version
550
+ * @see {@link EncoderPool} For reusing encoders across recurring resolutions
551
+ */
552
+ static encodeOneSync(encoderCodec, frame, options = {}) {
553
+ const env_2 = { stack: [], error: void 0, hasError: false };
554
+ try {
555
+ const encoder = __addDisposableResource(env_2, Encoder.createSync(encoderCodec, options), false);
556
+ const packets = [...encoder.encodeAllSync(frame), ...encoder.encodeAllSync(null)];
557
+ try {
558
+ const data = packets[0]?.data;
559
+ if (!data) {
560
+ throw new Error(`Encoder '${encoder.getCodec().name}' produced no output for frame`);
561
+ }
562
+ return data;
563
+ }
564
+ finally {
565
+ for (const packet of packets) {
566
+ packet.free();
567
+ }
568
+ }
569
+ }
570
+ catch (e_2) {
571
+ env_2.error = e_2;
572
+ env_2.hasError = true;
573
+ }
574
+ finally {
575
+ __disposeResources(env_2);
576
+ }
577
+ }
578
+ /**
579
+ * Check if encoder is open.
580
+ *
581
+ * @example
582
+ * ```typescript
583
+ * if (encoder.isEncoderOpen) {
584
+ * const packet = await encoder.encode(frame);
585
+ * }
586
+ * ```
587
+ */
588
+ get isEncoderOpen() {
589
+ return !this.isClosed;
590
+ }
591
+ /**
592
+ * Check if encoder has been initialized.
593
+ *
594
+ * Returns true after first frame has been processed and encoder opened.
595
+ * Useful for checking if encoder has received frame properties.
596
+ *
597
+ * @returns true if encoder has been initialized with frame data
598
+ *
599
+ * @example
600
+ * ```typescript
601
+ * if (!encoder.isEncoderInitialized) {
602
+ * console.log('Encoder will initialize on first frame');
603
+ * }
604
+ * ```
605
+ */
606
+ get isEncoderInitialized() {
607
+ return this.initialized;
608
+ }
609
+ /**
610
+ * Codec flags.
611
+ *
612
+ * @returns Current codec flags
613
+ *
614
+ * @throws {Error} If encoder is closed
615
+ *
616
+ * @example
617
+ * ```typescript
618
+ * const flags = encoder.codecFlags;
619
+ * console.log('Current flags:', flags);
620
+ * ```
621
+ *
622
+ * @see {@link setCodecFlags} To set flags
623
+ * @see {@link clearCodecFlags} To clear flags
624
+ * @see {@link hasCodecFlags} To check flags
625
+ */
626
+ get codecFlags() {
627
+ if (this.isClosed) {
628
+ throw new Error('Cannot get flags on closed encoder');
629
+ }
630
+ return this.codecContext.flags;
631
+ }
632
+ /**
633
+ * Set codec flags.
634
+ *
635
+ * @param flags - One or more flag values to set
636
+ *
637
+ * @throws {Error} If encoder is already initialized or closed
638
+ *
639
+ * @example
640
+ * ```typescript
641
+ * import { AV_CODEC_FLAG_GLOBAL_HEADER, AV_CODEC_FLAG_QSCALE } from 'node-av/constants';
642
+ *
643
+ * // Set multiple flags before initialization
644
+ * encoder.setCodecFlags(AV_CODEC_FLAG_GLOBAL_HEADER, AV_CODEC_FLAG_QSCALE);
645
+ * ```
646
+ *
647
+ * @see {@link clearCodecFlags} To clear flags
648
+ * @see {@link hasCodecFlags} To check flags
649
+ * @see {@link codecFlags} For direct flag access
650
+ */
651
+ setCodecFlags(...flags) {
652
+ if (this.isClosed) {
653
+ throw new Error('Cannot set flags on closed encoder');
654
+ }
655
+ if (this.initialized) {
656
+ throw new Error('Cannot set flags on already initialized encoder');
657
+ }
658
+ this.codecContext.setFlags(...flags);
659
+ }
660
+ /**
661
+ * Clear codec flags.
662
+ *
663
+ * @param flags - One or more flag values to clear
664
+ *
665
+ * @throws {Error} If encoder is already initialized or closed
666
+ *
667
+ * @example
668
+ * ```typescript
669
+ * import { AV_CODEC_FLAG_QSCALE } from 'node-av/constants';
670
+ *
671
+ * // Clear specific flag before initialization
672
+ * encoder.clearCodecFlags(AV_CODEC_FLAG_QSCALE);
673
+ * ```
674
+ *
675
+ * @see {@link setCodecFlags} To set flags
676
+ * @see {@link hasCodecFlags} To check flags
677
+ * @see {@link codecFlags} For direct flag access
678
+ */
679
+ clearCodecFlags(...flags) {
680
+ if (this.isClosed) {
681
+ throw new Error('Cannot clear flags on closed encoder');
682
+ }
683
+ if (this.initialized) {
684
+ throw new Error('Cannot clear flags on already initialized encoder');
685
+ }
686
+ this.codecContext.clearFlags(...flags);
687
+ }
688
+ /**
689
+ * Check if codec has specific flags.
690
+ *
691
+ * Tests whether all specified codec flags are set using bitwise AND.
692
+ *
693
+ * @param flags - One or more flag values to check
694
+ *
695
+ * @returns true if all specified flags are set, false otherwise
696
+ *
697
+ * @throws {Error} If encoder is closed
698
+ *
699
+ * @example
700
+ * ```typescript
701
+ * import { AV_CODEC_FLAG_GLOBAL_HEADER } from 'node-av/constants';
702
+ *
703
+ * if (encoder.hasCodecFlags(AV_CODEC_FLAG_GLOBAL_HEADER)) {
704
+ * console.log('Global header flag is set');
705
+ * }
706
+ * ```
707
+ *
708
+ * @see {@link setCodecFlags} To set flags
709
+ * @see {@link clearCodecFlags} To clear flags
710
+ * @see {@link codecFlags} For direct flag access
711
+ */
712
+ hasCodecFlags(...flags) {
713
+ if (this.isClosed) {
714
+ throw new Error('Cannot check flags on closed encoder');
715
+ }
716
+ return this.codecContext.hasFlags(...flags);
717
+ }
718
+ /**
719
+ * Check if encoder uses hardware acceleration.
720
+ *
721
+ * @returns true if hardware-accelerated
722
+ *
723
+ * @example
724
+ * ```typescript
725
+ * if (encoder.isHardware()) {
726
+ * console.log('Using GPU acceleration');
727
+ * }
728
+ * ```
729
+ *
730
+ * @see {@link HardwareContext} For hardware setup
731
+ */
732
+ isHardware() {
733
+ return this.codec.isHardwareAcceleratedEncoder();
734
+ }
735
+ /**
736
+ * Check if encoder is ready for processing.
737
+ *
738
+ * @returns true if initialized and ready
739
+ *
740
+ * @example
741
+ * ```typescript
742
+ * if (encoder.isReady()) {
743
+ * const packet = await encoder.encode(frame);
744
+ * }
745
+ * ```
746
+ */
747
+ isReady() {
748
+ return this.initialized && !this.isClosed;
749
+ }
750
+ /**
751
+ * Encode the next frame as a keyframe.
752
+ *
753
+ * Answers receiver feedback such as an RTCP PLI, so a receiver that lost
754
+ * packets resyncs on the next frame instead of waiting for the next GOP.
755
+ * Set the encoder's `forced-idr` option where it has one, otherwise some
756
+ * encoders (libx264, NVENC, QSV, AMF) emit an I frame that is not an IDR.
757
+ *
758
+ * @example
759
+ * ```typescript
760
+ * sender.onPictureLossIndication.subscribe(() => {
761
+ * encoder.requestKeyframe();
762
+ * });
763
+ * ```
764
+ */
765
+ requestKeyframe() {
766
+ this.keyframeRequested = true;
767
+ }
768
+ /**
769
+ * Send a frame to the encoder.
770
+ *
771
+ * Sends a raw frame to the encoder for encoding.
772
+ * Does not return encoded packets - use {@link receive} to retrieve packets.
773
+ * On first frame, automatically initializes encoder with frame properties.
774
+ * A single frame can produce zero, one, or multiple packets depending on codec buffering.
775
+ *
776
+ * **Important**: This method only SENDS the frame to the encoder.
777
+ * You must call {@link receive} separately (potentially multiple times) to get encoded packets.
778
+ *
779
+ * Like FFmpeg CLI, a video frame whose timestamp lands on the previous frame's
780
+ * in the encoder time base moves to the next tick, and one that lies entirely
781
+ * before it is dropped, since encoders need strictly increasing timestamps.
782
+ *
783
+ * Direct mapping to avcodec_send_frame().
784
+ *
785
+ * @param frame - Raw frame to send to encoder, or null to flush
786
+ *
787
+ * @throws {FFmpegError} If sending frame fails
788
+ *
789
+ * @example
790
+ * ```typescript
791
+ * // Send frame and receive packets
792
+ * await encoder.encode(frame);
793
+ *
794
+ * // Receive all available packets
795
+ * while (true) {
796
+ * const packet = await encoder.receive();
797
+ * if (!packet) break;
798
+ * console.log(`Encoded packet with PTS: ${packet.pts}`);
799
+ * await output.writePacket(packet);
800
+ * packet.free();
801
+ * }
802
+ * ```
803
+ *
804
+ * @example
805
+ * ```typescript
806
+ * for await (const frame of decoder.frames(input.packets())) {
807
+ * // Send frame
808
+ * await encoder.encode(frame);
809
+ *
810
+ * // Receive available packets
811
+ * let packet;
812
+ * while ((packet = await encoder.receive())) {
813
+ * await output.writePacket(packet);
814
+ * packet.free();
815
+ * }
816
+ * frame.free();
817
+ * }
818
+ * ```
819
+ *
820
+ * @see {@link receive} For receiving encoded packets
821
+ * @see {@link encodeAll} For combined send+receive operation
822
+ * @see {@link packets} For automatic frame iteration
823
+ * @see {@link flush} For end-of-stream handling
824
+ * @see {@link encodeSync} For synchronous version
825
+ */
826
+ async encode(frame) {
827
+ this.signal?.throwIfAborted();
828
+ if (this.isClosed) {
829
+ return;
830
+ }
831
+ // Null frame = flush encoder
832
+ if (frame === null) {
833
+ await this.flush();
834
+ return;
835
+ }
836
+ // Open encoder if not already done
837
+ this.initializePromise ??= this.initialize(frame);
838
+ await this.initializePromise;
839
+ // Give an unspecified-layout frame the concrete native layout the codec was
840
+ // opened with (and the resampler configured for), so both accept it.
841
+ if (this.audioInputLayout) {
842
+ frame.channelLayout = this.audioInputLayout;
843
+ }
844
+ // Convert to the codec's format first (audio resample / video pixfmt).
845
+ const input = this.audioResampler ? this.resampleAudio(frame) : this.videoScaler ? this.scaleVideo(frame) : frame;
846
+ // Prepare frame for encoding (timestamps, quality, channel count); a video
847
+ // frame that falls behind the previous one is dropped
848
+ if (!this.prepareFrameForEncoding(input)) {
849
+ return;
850
+ }
851
+ const encode = async (newFrame) => {
852
+ const sendRet = await this.codecContext.sendFrame(newFrame);
853
+ if (sendRet < 0 && sendRet !== AVERROR_EOF) {
854
+ FFmpegError.throwIfError(sendRet, 'Failed to send frame to encoder');
855
+ return;
856
+ }
857
+ };
858
+ if (this.audioFrameBuffer) {
859
+ // Push frame into buffer - actual sending happens in receive()
860
+ await this.audioFrameBuffer.push(input);
861
+ }
862
+ else {
863
+ await encode(input);
864
+ }
865
+ }
866
+ /**
867
+ * Send a frame to the encoder synchronously.
868
+ * Synchronous version of encode.
869
+ *
870
+ * Sends a raw frame to the encoder for encoding.
871
+ * Does not return encoded packets - use {@link receiveSync} to retrieve packets.
872
+ * On first frame, automatically initializes encoder with frame properties.
873
+ * A single frame can produce zero, one, or multiple packets depending on codec buffering.
874
+ *
875
+ * **Important**: This method only SENDS the frame to the encoder.
876
+ * You must call {@link receiveSync} separately (potentially multiple times) to get encoded packets.
877
+ *
878
+ * Like FFmpeg CLI, a video frame whose timestamp lands on the previous frame's
879
+ * in the encoder time base moves to the next tick, and one that lies entirely
880
+ * before it is dropped, since encoders need strictly increasing timestamps.
881
+ *
882
+ * Direct mapping to avcodec_send_frame().
883
+ *
884
+ * @param frame - Raw frame to send to encoder, or null to flush
885
+ *
886
+ * @throws {FFmpegError} If sending frame fails
887
+ *
888
+ * @example
889
+ * ```typescript
890
+ * // Send frame and receive packets
891
+ * encoder.encodeSync(frame);
892
+ *
893
+ * // Receive all available packets
894
+ * let packet;
895
+ * while ((packet = encoder.receiveSync())) {
896
+ * console.log(`Encoded packet with PTS: ${packet.pts}`);
897
+ * output.writePacketSync(packet);
898
+ * packet.free();
899
+ * }
900
+ * ```
901
+ *
902
+ * @see {@link receiveSync} For receiving encoded packets
903
+ * @see {@link encodeAllSync} For combined send+receive operation
904
+ * @see {@link packetsSync} For automatic frame iteration
905
+ * @see {@link flushSync} For end-of-stream handling
906
+ * @see {@link encode} For async version
907
+ */
908
+ encodeSync(frame) {
909
+ if (this.isClosed) {
910
+ return;
911
+ }
912
+ // Null frame = flush encoder
913
+ if (frame === null) {
914
+ this.flushSync();
915
+ return;
916
+ }
917
+ // Open encoder if not already done
918
+ if (!this.initialized) {
919
+ this.initializeSync(frame);
920
+ }
921
+ // Give an unspecified-layout frame the concrete native layout the codec was
922
+ // opened with (and the resampler configured for), so both accept it.
923
+ if (this.audioInputLayout) {
924
+ frame.channelLayout = this.audioInputLayout;
925
+ }
926
+ // Convert to the codec's format first (audio resample / video pixfmt).
927
+ const input = this.audioResampler ? this.resampleAudio(frame) : this.videoScaler ? this.scaleVideo(frame) : frame;
928
+ if (!this.prepareFrameForEncoding(input)) {
929
+ return;
930
+ }
931
+ const encode = (newFrame) => {
932
+ const sendRet = this.codecContext.sendFrameSync(newFrame);
933
+ if (sendRet < 0 && sendRet !== AVERROR_EOF) {
934
+ FFmpegError.throwIfError(sendRet, 'Failed to send frame to encoder');
935
+ return;
936
+ }
937
+ };
938
+ if (this.audioFrameBuffer) {
939
+ // Push frame into buffer - actual sending happens in receiveSync()
940
+ this.audioFrameBuffer.pushSync(input);
941
+ }
942
+ else {
943
+ encode(input);
944
+ }
945
+ }
946
+ /**
947
+ * Encode a frame to packets.
948
+ *
949
+ * Sends a frame to the encoder and receives all available encoded packets.
950
+ * Returns array of packets - may be empty if encoder needs more data.
951
+ * On first frame, automatically initializes encoder with frame properties.
952
+ * One frame can produce zero, one, or multiple packets depending on codec.
953
+ *
954
+ * Direct mapping to avcodec_send_frame() and avcodec_receive_packet().
955
+ *
956
+ * @param frame - Raw frame to encode (or null to flush)
957
+ *
958
+ * @returns Array of encoded packets (empty if more data needed or encoder is closed)
959
+ *
960
+ * @throws {FFmpegError} If encoding fails
961
+ *
962
+ * @example
963
+ * ```typescript
964
+ * const packets = await encoder.encodeAll(frame);
965
+ * for (const packet of packets) {
966
+ * console.log(`Encoded packet with PTS: ${packet.pts}`);
967
+ * await output.writePacket(packet);
968
+ * packet.free();
969
+ * }
970
+ * ```
971
+ *
972
+ * @example
973
+ * ```typescript
974
+ * // Encode loop
975
+ * for await (const frame of decoder.frames(input.packets())) {
976
+ * const packets = await encoder.encodeAll(frame);
977
+ * for (const packet of packets) {
978
+ * await output.writePacket(packet);
979
+ * packet.free();
980
+ * }
981
+ * frame.free();
982
+ * }
983
+ * ```
984
+ *
985
+ * @see {@link encode} For single packet encoding
986
+ * @see {@link packets} For automatic frame iteration
987
+ * @see {@link flush} For end-of-stream handling
988
+ * @see {@link encodeAllSync} For synchronous version
989
+ */
990
+ async encodeAll(frame) {
991
+ this.signal?.throwIfAborted();
992
+ await this.encode(frame);
993
+ // Receive all available packets
994
+ const packets = [];
995
+ while (true) {
996
+ const packet = await this.receive();
997
+ if (!packet)
998
+ break; // Stop on EAGAIN or EOF
999
+ packets.push(packet); // Only push actual packets
1000
+ }
1001
+ return packets;
1002
+ }
1003
+ /**
1004
+ * Encode a frame to packets synchronously.
1005
+ * Synchronous version of encodeAll.
1006
+ *
1007
+ * Sends a frame to the encoder and receives all available encoded packets.
1008
+ * Returns array of packets - may be empty if encoder needs more data.
1009
+ * On first frame, automatically initializes encoder with frame properties.
1010
+ * One frame can produce zero, one, or multiple packets depending on codec.
1011
+ *
1012
+ * Direct mapping to avcodec_send_frame() and avcodec_receive_packet().
1013
+ *
1014
+ * @param frame - Raw frame to encode (or null to flush)
1015
+ *
1016
+ * @returns Array of encoded packets (empty if more data needed or encoder is closed)
1017
+ *
1018
+ * @throws {FFmpegError} If encoding fails
1019
+ *
1020
+ * @example
1021
+ * ```typescript
1022
+ * const packets = encoder.encodeAllSync(frame);
1023
+ * for (const packet of packets) {
1024
+ * console.log(`Encoded packet with PTS: ${packet.pts}`);
1025
+ * output.writePacketSync(packet);
1026
+ * packet.free();
1027
+ * }
1028
+ * ```
1029
+ *
1030
+ * @example
1031
+ * ```typescript
1032
+ * // Encode loop
1033
+ * for (const frame of decoder.framesSync(packets)) {
1034
+ * const packets = encoder.encodeAllSync(frame);
1035
+ * for (const packet of packets) {
1036
+ * output.writePacketSync(packet);
1037
+ * packet.free();
1038
+ * }
1039
+ * frame.free();
1040
+ * }
1041
+ * ```
1042
+ *
1043
+ * @see {@link encodeSync} For single packet encoding
1044
+ * @see {@link packetsSync} For automatic frame iteration
1045
+ * @see {@link flushSync} For end-of-stream handling
1046
+ * @see {@link encodeAll} For async version
1047
+ */
1048
+ encodeAllSync(frame) {
1049
+ this.encodeSync(frame);
1050
+ // Receive all available packets
1051
+ const packets = [];
1052
+ while (true) {
1053
+ const packet = this.receiveSync();
1054
+ if (!packet)
1055
+ break; // Stop on EAGAIN or EOF
1056
+ packets.push(packet); // Only push actual packets
1057
+ }
1058
+ return packets;
1059
+ }
1060
+ /**
1061
+ * Encode frame stream to packet stream.
1062
+ *
1063
+ * High-level async generator for complete encoding pipeline.
1064
+ * Encoder is only flushed when EOF (null) signal is explicitly received.
1065
+ * Primary interface for stream-based encoding.
1066
+ *
1067
+ * **EOF Handling:**
1068
+ * - Send null to flush encoder and get remaining buffered packets
1069
+ * - Generator yields null after flushing when null is received
1070
+ * - No automatic flushing - encoder stays open until EOF or close()
1071
+ *
1072
+ * @param frames - Async iterable of frames, single frame, or null to flush
1073
+ *
1074
+ * @yields {Packet | null} Encoded packets, followed by null when explicitly flushed
1075
+ *
1076
+ * @throws {FFmpegError} If encoding fails
1077
+ *
1078
+ * @example
1079
+ * ```typescript
1080
+ * // Stream of frames with automatic EOF propagation
1081
+ * for await (const packet of encoder.packets(decoder.frames(input.packets()))) {
1082
+ * if (packet === null) {
1083
+ * console.log('Encoder flushed');
1084
+ * break;
1085
+ * }
1086
+ * await output.writePacket(packet);
1087
+ * packet.free(); // Must free output packets
1088
+ * }
1089
+ * ```
1090
+ *
1091
+ * @example
1092
+ * ```typescript
1093
+ * // Single frame - no automatic flush
1094
+ * for await (const packet of encoder.packets(singleFrame)) {
1095
+ * await output.writePacket(packet);
1096
+ * packet.free();
1097
+ * }
1098
+ * // Encoder remains open, buffered packets not flushed
1099
+ * ```
1100
+ *
1101
+ * @example
1102
+ * ```typescript
1103
+ * // Explicit flush with EOF
1104
+ * for await (const packet of encoder.packets(null)) {
1105
+ * if (packet === null) {
1106
+ * console.log('All buffered packets flushed');
1107
+ * break;
1108
+ * }
1109
+ * console.log('Buffered packet:', packet.pts);
1110
+ * await output.writePacket(packet);
1111
+ * packet.free();
1112
+ * }
1113
+ * ```
1114
+ *
1115
+ * @see {@link encode} For single frame encoding
1116
+ * @see {@link Decoder.frames} For frame source
1117
+ * @see {@link packetsSync} For sync version
1118
+ */
1119
+ async *packets(frames) {
1120
+ const self = this;
1121
+ const processFrame = async function* (frame) {
1122
+ await self.encode(frame);
1123
+ while (true) {
1124
+ const packet = await self.receive();
1125
+ if (!packet)
1126
+ break;
1127
+ yield packet;
1128
+ }
1129
+ }.bind(this);
1130
+ const finalize = async function* () {
1131
+ for await (const remaining of self.flushPackets()) {
1132
+ yield remaining;
1133
+ }
1134
+ yield null;
1135
+ }.bind(this);
1136
+ if (frames === null) {
1137
+ yield* finalize();
1138
+ return;
1139
+ }
1140
+ if (frames instanceof Frame) {
1141
+ yield* processFrame(frames);
1142
+ return;
1143
+ }
1144
+ for await (const frame_1 of frames) {
1145
+ const env_3 = { stack: [], error: void 0, hasError: false };
1146
+ try {
1147
+ const frame = __addDisposableResource(env_3, frame_1, false);
1148
+ this.signal?.throwIfAborted();
1149
+ if (frame === null) {
1150
+ yield* finalize();
1151
+ return;
1152
+ }
1153
+ yield* processFrame(frame);
1154
+ }
1155
+ catch (e_3) {
1156
+ env_3.error = e_3;
1157
+ env_3.hasError = true;
1158
+ }
1159
+ finally {
1160
+ __disposeResources(env_3);
1161
+ }
1162
+ }
1163
+ }
1164
+ /**
1165
+ * Encode frame stream to packet stream synchronously.
1166
+ * Synchronous version of packets.
1167
+ *
1168
+ * High-level sync generator for complete encoding pipeline.
1169
+ * Encoder is only flushed when EOF (null) signal is explicitly received.
1170
+ * Primary interface for stream-based encoding.
1171
+ *
1172
+ * **EOF Handling:**
1173
+ * - Send null to flush encoder and get remaining buffered packets
1174
+ * - Generator yields null after flushing when null is received
1175
+ * - No automatic flushing - encoder stays open until EOF or close()
1176
+ *
1177
+ * @param frames - Iterable of frames, single frame, or null to flush
1178
+ *
1179
+ * @yields {Packet | null} Encoded packets, followed by null when explicitly flushed
1180
+ *
1181
+ * @throws {FFmpegError} If encoding fails
1182
+ *
1183
+ * @example
1184
+ * ```typescript
1185
+ * // Stream of frames with automatic EOF propagation
1186
+ * for (const packet of encoder.packetsSync(decoder.framesSync(packets))) {
1187
+ * if (packet === null) {
1188
+ * console.log('Encoder flushed');
1189
+ * break;
1190
+ * }
1191
+ * output.writePacketSync(packet);
1192
+ * packet.free(); // Must free output packets
1193
+ * }
1194
+ * ```
1195
+ *
1196
+ * @example
1197
+ * ```typescript
1198
+ * // Single frame - no automatic flush
1199
+ * for (const packet of encoder.packetsSync(singleFrame)) {
1200
+ * output.writePacketSync(packet);
1201
+ * packet.free();
1202
+ * }
1203
+ * // Encoder remains open, buffered packets not flushed
1204
+ * ```
1205
+ *
1206
+ * @example
1207
+ * ```typescript
1208
+ * // Explicit flush with EOF
1209
+ * for (const packet of encoder.packetsSync(null)) {
1210
+ * if (packet === null) {
1211
+ * console.log('All buffered packets flushed');
1212
+ * break;
1213
+ * }
1214
+ * console.log('Buffered packet:', packet.pts);
1215
+ * output.writePacketSync(packet);
1216
+ * packet.free();
1217
+ * }
1218
+ * ```
1219
+ *
1220
+ * @see {@link encodeSync} For single frame encoding
1221
+ * @see {@link Decoder.framesSync} For frame source
1222
+ * @see {@link packets} For async version
1223
+ */
1224
+ *packetsSync(frames) {
1225
+ const self = this;
1226
+ // Helper: Encode frame and yield all available packets (filters out EAGAIN nulls and EOF)
1227
+ const processFrame = function* (frame) {
1228
+ self.encodeSync(frame);
1229
+ // Receive ALL packets (filter out null/EAGAIN and EOF)
1230
+ while (true) {
1231
+ const packet = self.receiveSync();
1232
+ if (!packet)
1233
+ break; // EAGAIN or EOF - no more packets available
1234
+ yield packet; // Only yield actual packets
1235
+ }
1236
+ }.bind(this);
1237
+ // Helper: Flush encoder and signal EOF
1238
+ const finalize = function* () {
1239
+ for (const remaining of self.flushPacketsSync()) {
1240
+ yield remaining; // Only yield actual packets
1241
+ }
1242
+ yield null; // Signal end-of-stream
1243
+ }.bind(this);
1244
+ // Case 1: EOF input -> flush only
1245
+ if (frames === null) {
1246
+ yield* finalize();
1247
+ return;
1248
+ }
1249
+ // Case 2: Single frame - NO AUTOMATIC FLUSH
1250
+ if (frames instanceof Frame) {
1251
+ yield* processFrame(frames);
1252
+ return; // No finalize() call!
1253
+ }
1254
+ // Case 3: Iterable of frames
1255
+ for (const frame_2 of frames) {
1256
+ const env_4 = { stack: [], error: void 0, hasError: false };
1257
+ try {
1258
+ const frame = __addDisposableResource(env_4, frame_2, false);
1259
+ // Check for EOF signal from upstream
1260
+ if (frame === null) {
1261
+ yield* finalize();
1262
+ return;
1263
+ }
1264
+ yield* processFrame(frame);
1265
+ }
1266
+ catch (e_4) {
1267
+ env_4.error = e_4;
1268
+ env_4.hasError = true;
1269
+ }
1270
+ finally {
1271
+ __disposeResources(env_4);
1272
+ }
1273
+ }
1274
+ // No fallback flush - only flush on explicit EOF
1275
+ }
1276
+ /**
1277
+ * Flush encoder and signal end-of-stream.
1278
+ *
1279
+ * Sends null frame to encoder to signal end-of-stream.
1280
+ * Does nothing if encoder was never initialized or is closed.
1281
+ * Must call receive() to get remaining buffered packets.
1282
+ *
1283
+ * Direct mapping to avcodec_send_frame(NULL).
1284
+ *
1285
+ * @example
1286
+ * ```typescript
1287
+ * // Signal end of stream
1288
+ * await encoder.flush();
1289
+ *
1290
+ * // Then get remaining packets
1291
+ * let packet;
1292
+ * while ((packet = await encoder.receive()) !== null) {
1293
+ * console.log('Got buffered packet');
1294
+ * await output.writePacket(packet);
1295
+ * packet.free();
1296
+ * }
1297
+ * ```
1298
+ *
1299
+ * @see {@link flushPackets} For async iteration
1300
+ * @see {@link receive} For getting buffered packets
1301
+ * @see {@link flushSync} For synchronous version
1302
+ */
1303
+ async flush() {
1304
+ this.signal?.throwIfAborted();
1305
+ if (this.isClosed || !this.initialized) {
1306
+ return;
1307
+ }
1308
+ // Drain samples buffered inside the resampler into the FIFO/encoder first.
1309
+ const drained = this.drainResampler();
1310
+ if (drained) {
1311
+ if (this.audioFrameBuffer) {
1312
+ await this.audioFrameBuffer.push(drained);
1313
+ }
1314
+ else {
1315
+ await this.codecContext.sendFrame(drained);
1316
+ }
1317
+ }
1318
+ // If using AudioFrameBuffer, flush remaining buffered samples first
1319
+ if (this.audioFrameBuffer) {
1320
+ // Drain all complete frames
1321
+ let _bufferedFrame;
1322
+ while (!this.isClosed && (_bufferedFrame = await this.audioFrameBuffer.pull()) !== null) {
1323
+ const env_5 = { stack: [], error: void 0, hasError: false };
1324
+ try {
1325
+ const bufferedFrame = __addDisposableResource(env_5, _bufferedFrame, false);
1326
+ await this.codecContext.sendFrame(bufferedFrame);
1327
+ }
1328
+ catch (e_5) {
1329
+ env_5.error = e_5;
1330
+ env_5.hasError = true;
1331
+ }
1332
+ finally {
1333
+ __disposeResources(env_5);
1334
+ }
1335
+ }
1336
+ // Encode the final partial frame padded with silence - otherwise the tail
1337
+ // (up to frameSize - 1 samples, ~21 ms at 48 kHz/1024) would be dropped.
1338
+ const _partialFrame = !this.isClosed ? await this.audioFrameBuffer.pullPartial() : null;
1339
+ if (_partialFrame) {
1340
+ const env_6 = { stack: [], error: void 0, hasError: false };
1341
+ try {
1342
+ const partialFrame = __addDisposableResource(env_6, _partialFrame, false);
1343
+ await this.codecContext.sendFrame(partialFrame);
1344
+ }
1345
+ catch (e_6) {
1346
+ env_6.error = e_6;
1347
+ env_6.hasError = true;
1348
+ }
1349
+ finally {
1350
+ __disposeResources(env_6);
1351
+ }
1352
+ }
1353
+ }
1354
+ // Send flush frame (null)
1355
+ const ret = await this.codecContext.sendFrame(null);
1356
+ if (ret < 0 && ret !== AVERROR_EOF) {
1357
+ if (ret !== AVERROR_EAGAIN) {
1358
+ FFmpegError.throwIfError(ret, 'Failed to flush encoder');
1359
+ }
1360
+ }
1361
+ }
1362
+ /**
1363
+ * Flush encoder and signal end-of-stream synchronously.
1364
+ * Synchronous version of flush.
1365
+ *
1366
+ * Sends null frame to encoder to signal end-of-stream.
1367
+ * Does nothing if encoder was never initialized or is closed.
1368
+ * Must call receiveSync() to get remaining buffered packets.
1369
+ *
1370
+ * Direct mapping to avcodec_send_frame(NULL).
1371
+ *
1372
+ * @example
1373
+ * ```typescript
1374
+ * // Signal end of stream
1375
+ * encoder.flushSync();
1376
+ *
1377
+ * // Then get remaining packets
1378
+ * let packet;
1379
+ * while ((packet = encoder.receiveSync()) !== null) {
1380
+ * console.log('Got buffered packet');
1381
+ * output.writePacketSync(packet);
1382
+ * packet.free();
1383
+ * }
1384
+ * ```
1385
+ *
1386
+ * @see {@link flushPacketsSync} For sync iteration
1387
+ * @see {@link receiveSync} For getting buffered packets
1388
+ * @see {@link flush} For async version
1389
+ */
1390
+ flushSync() {
1391
+ if (this.isClosed || !this.initialized) {
1392
+ return;
1393
+ }
1394
+ // Drain samples buffered inside the resampler into the FIFO/encoder first.
1395
+ const drained = this.drainResampler();
1396
+ if (drained) {
1397
+ if (this.audioFrameBuffer) {
1398
+ this.audioFrameBuffer.pushSync(drained);
1399
+ }
1400
+ else {
1401
+ this.codecContext.sendFrameSync(drained);
1402
+ }
1403
+ }
1404
+ // If using AudioFrameBuffer, flush remaining buffered samples first
1405
+ if (this.audioFrameBuffer) {
1406
+ // Drain all complete frames
1407
+ let _bufferedFrame;
1408
+ while (!this.isClosed && (_bufferedFrame = this.audioFrameBuffer.pullSync()) !== null) {
1409
+ const env_7 = { stack: [], error: void 0, hasError: false };
1410
+ try {
1411
+ const bufferedFrame = __addDisposableResource(env_7, _bufferedFrame, false);
1412
+ this.codecContext.sendFrameSync(bufferedFrame);
1413
+ }
1414
+ catch (e_7) {
1415
+ env_7.error = e_7;
1416
+ env_7.hasError = true;
1417
+ }
1418
+ finally {
1419
+ __disposeResources(env_7);
1420
+ }
1421
+ }
1422
+ // Encode the final partial frame padded with silence (see flush)
1423
+ const _partialFrame = !this.isClosed ? this.audioFrameBuffer.pullPartialSync() : null;
1424
+ if (_partialFrame) {
1425
+ const env_8 = { stack: [], error: void 0, hasError: false };
1426
+ try {
1427
+ const partialFrame = __addDisposableResource(env_8, _partialFrame, false);
1428
+ this.codecContext.sendFrameSync(partialFrame);
1429
+ }
1430
+ catch (e_8) {
1431
+ env_8.error = e_8;
1432
+ env_8.hasError = true;
1433
+ }
1434
+ finally {
1435
+ __disposeResources(env_8);
1436
+ }
1437
+ }
1438
+ }
1439
+ // Send flush frame (null)
1440
+ const ret = this.codecContext.sendFrameSync(null);
1441
+ if (ret < 0 && ret !== AVERROR_EOF) {
1442
+ if (ret !== AVERROR_EAGAIN) {
1443
+ FFmpegError.throwIfError(ret, 'Failed to flush encoder');
1444
+ }
1445
+ }
1446
+ }
1447
+ /**
1448
+ * Flush all buffered packets as async generator.
1449
+ *
1450
+ * Convenient async iteration over remaining packets.
1451
+ * Automatically handles flush and repeated receive calls.
1452
+ * Returns immediately if encoder was never initialized or is closed.
1453
+ *
1454
+ * @yields {Packet} Buffered packets
1455
+ *
1456
+ * @example
1457
+ * ```typescript
1458
+ * // Flush at end of encoding
1459
+ * for await (const packet of encoder.flushPackets()) {
1460
+ * console.log('Processing buffered packet');
1461
+ * await output.writePacket(packet);
1462
+ * packet.free();
1463
+ * }
1464
+ * ```
1465
+ *
1466
+ * @see {@link encode} For sending frames and receiving packets
1467
+ * @see {@link flush} For signaling end-of-stream
1468
+ * @see {@link flushPacketsSync} For synchronous version
1469
+ */
1470
+ async *flushPackets() {
1471
+ // Send flush signal
1472
+ await this.flush();
1473
+ // Yield all remaining packets (filter out null/EAGAIN and EOF)
1474
+ while (true) {
1475
+ const packet = await this.receive();
1476
+ if (!packet)
1477
+ break; // Stop on EAGAIN or EOF
1478
+ yield packet; // Only yield actual packets
1479
+ }
1480
+ }
1481
+ /**
1482
+ * Flush all buffered packets as generator synchronously.
1483
+ * Synchronous version of flushPackets.
1484
+ *
1485
+ * Convenient sync iteration over remaining packets.
1486
+ * Automatically handles flush and repeated receive calls.
1487
+ * Returns immediately if encoder was never initialized or is closed.
1488
+ *
1489
+ * @yields {Packet} Buffered packets
1490
+ *
1491
+ * @example
1492
+ * ```typescript
1493
+ * // Flush at end of encoding
1494
+ * for (const packet of encoder.flushPacketsSync()) {
1495
+ * console.log('Processing buffered packet');
1496
+ * output.writePacketSync(packet);
1497
+ * packet.free();
1498
+ * }
1499
+ * ```
1500
+ *
1501
+ * @see {@link encodeSync} For sending frames and receiving packets
1502
+ * @see {@link flushSync} For signaling end-of-stream
1503
+ * @see {@link flushPackets} For async version
1504
+ */
1505
+ *flushPacketsSync() {
1506
+ // Send flush signal
1507
+ this.flushSync();
1508
+ // Yield all remaining packets (filter out null/EAGAIN and EOF)
1509
+ while (true) {
1510
+ const packet = this.receiveSync();
1511
+ if (!packet)
1512
+ break; // Stop on EAGAIN or EOF
1513
+ yield packet; // Only yield actual packets
1514
+ }
1515
+ }
1516
+ /**
1517
+ * Receive packet from encoder.
1518
+ *
1519
+ * Gets encoded packets from the codec's internal buffer.
1520
+ * Handles packet cloning and error checking.
1521
+ * Implements FFmpeg's send/receive pattern.
1522
+ *
1523
+ * **Return Values:**
1524
+ * - `Packet` - Successfully encoded packet (AVERROR >= 0)
1525
+ * - `null` - Need more input frames (AVERROR_EAGAIN), or encoder not initialized
1526
+ * - `undefined` - End of stream reached (AVERROR_EOF), or encoder is closed
1527
+ *
1528
+ * Direct mapping to avcodec_receive_packet().
1529
+ *
1530
+ * @returns Cloned packet, null if need more data, or undefined if stream ended
1531
+ *
1532
+ * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
1533
+ *
1534
+ * @throws {Error} If packet cloning fails (out of memory)
1535
+ *
1536
+ * @example
1537
+ * ```typescript
1538
+ * // Process all buffered packets
1539
+ * while (true) {
1540
+ * const packet = await encoder.receive();
1541
+ * if (!packet) break; // Stop on EAGAIN or EOF
1542
+ * console.log(`Got packet with PTS: ${packet.pts}`);
1543
+ * await output.writePacket(packet);
1544
+ * packet.free();
1545
+ * }
1546
+ * ```
1547
+ *
1548
+ * @example
1549
+ * ```typescript
1550
+ * // Handle each return value explicitly
1551
+ * const packet = await encoder.receive();
1552
+ * if (packet === EOF) {
1553
+ * console.log('Encoder stream ended');
1554
+ * } else if (packet === null) {
1555
+ * console.log('Need more input frames');
1556
+ * } else {
1557
+ * console.log(`Got packet: pts=${packet.pts}`);
1558
+ * await output.writePacket(packet);
1559
+ * packet.free();
1560
+ * }
1561
+ * ```
1562
+ *
1563
+ * @see {@link encode} For sending frames and receiving packets
1564
+ * @see {@link flush} For signaling end-of-stream
1565
+ * @see {@link receiveSync} For synchronous version
1566
+ * @see {@link EOF} For end-of-stream signal
1567
+ */
1568
+ async receive() {
1569
+ if (this.isClosed) {
1570
+ return EOF;
1571
+ }
1572
+ if (!this.initialized) {
1573
+ return null;
1574
+ }
1575
+ // Clear previous packet data
1576
+ this.packet.unref();
1577
+ // Skip frames the codec rejects (e.g. aac "Input contains (near) NaN/+-Inf"). Pulling a single
1578
+ // frame per call would leave the rest in the FIFO while every encode() adds more.
1579
+ while (this.audioFrameBuffer?.hasFrame()) {
1580
+ const env_9 = { stack: [], error: void 0, hasError: false };
1581
+ try {
1582
+ const bufferedFrame = __addDisposableResource(env_9, await this.audioFrameBuffer.pull(), false);
1583
+ if (!bufferedFrame) {
1584
+ break;
1585
+ }
1586
+ const sendRet = await this.codecContext.sendFrame(bufferedFrame);
1587
+ if (sendRet >= 0 || sendRet === AVERROR_EAGAIN) {
1588
+ break;
1589
+ }
1590
+ }
1591
+ catch (e_9) {
1592
+ env_9.error = e_9;
1593
+ env_9.hasError = true;
1594
+ }
1595
+ finally {
1596
+ __disposeResources(env_9);
1597
+ }
1598
+ }
1599
+ const ret = await this.codecContext.receivePacket(this.packet);
1600
+ if (ret === 0) {
1601
+ // Set packet timebase to codec timebase
1602
+ this.packet.timeBase = this.codecContext.timeBase;
1603
+ // Mark packet as trusted (from encoder)
1604
+ this.packet.setFlags(AV_PKT_FLAG_TRUSTED);
1605
+ // Got a packet, clone it for the user
1606
+ const cloned = this.packet.clone();
1607
+ if (!cloned) {
1608
+ throw new Error('Failed to clone packet (out of memory)');
1609
+ }
1610
+ return cloned;
1611
+ }
1612
+ else if (ret === AVERROR_EAGAIN) {
1613
+ // Need more data
1614
+ return null;
1615
+ }
1616
+ else if (ret === AVERROR_EOF) {
1617
+ // End of stream
1618
+ return EOF;
1619
+ }
1620
+ else {
1621
+ // Error
1622
+ FFmpegError.throwIfError(ret, 'Failed to receive packet');
1623
+ return null;
1624
+ }
1625
+ }
1626
+ /**
1627
+ * Receive packet from encoder synchronously.
1628
+ * Synchronous version of receive.
1629
+ *
1630
+ * Gets encoded packets from the codec's internal buffer.
1631
+ * Handles packet cloning and error checking.
1632
+ * Implements FFmpeg's send/receive pattern.
1633
+ *
1634
+ * **Return Values:**
1635
+ * - `Packet` - Successfully encoded packet (AVERROR >= 0)
1636
+ * - `null` - Need more input frames (AVERROR_EAGAIN), or encoder not initialized
1637
+ * - `undefined` - End of stream reached (AVERROR_EOF), or encoder is closed
1638
+ *
1639
+ * Direct mapping to avcodec_receive_packet().
1640
+ *
1641
+ * @returns Cloned packet, null if need more data, or undefined if stream ended
1642
+ *
1643
+ * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
1644
+ *
1645
+ * @throws {Error} If packet cloning fails (out of memory)
1646
+ *
1647
+ * @example
1648
+ * ```typescript
1649
+ * // Process all buffered packets
1650
+ * while (true) {
1651
+ * const packet = encoder.receiveSync();
1652
+ * if (!packet) break; // Stop on EAGAIN or EOF
1653
+ * console.log(`Got packet with PTS: ${packet.pts}`);
1654
+ * output.writePacketSync(packet);
1655
+ * packet.free();
1656
+ * }
1657
+ * ```
1658
+ *
1659
+ * @example
1660
+ * ```typescript
1661
+ * // Handle each return value explicitly
1662
+ * const packet = encoder.receiveSync();
1663
+ * if (packet === EOF) {
1664
+ * console.log('Encoder stream ended');
1665
+ * } else if (packet === null) {
1666
+ * console.log('Need more input frames');
1667
+ * } else {
1668
+ * console.log(`Got packet: pts=${packet.pts}`);
1669
+ * output.writePacketSync(packet);
1670
+ * packet.free();
1671
+ * }
1672
+ * ```
1673
+ *
1674
+ * @see {@link encodeSync} For sending frames and receiving packets
1675
+ * @see {@link flushSync} For signaling end-of-stream
1676
+ * @see {@link receive} For async version
1677
+ * @see {@link EOF} For end-of-stream signal
1678
+ */
1679
+ receiveSync() {
1680
+ if (this.isClosed) {
1681
+ return EOF;
1682
+ }
1683
+ if (!this.initialized) {
1684
+ return null;
1685
+ }
1686
+ // Clear previous packet data
1687
+ this.packet.unref();
1688
+ // Skip frames the codec rejects, see receive()
1689
+ while (this.audioFrameBuffer?.hasFrame()) {
1690
+ const env_10 = { stack: [], error: void 0, hasError: false };
1691
+ try {
1692
+ const bufferedFrame = __addDisposableResource(env_10, this.audioFrameBuffer.pullSync(), false);
1693
+ if (!bufferedFrame) {
1694
+ break;
1695
+ }
1696
+ const sendRet = this.codecContext.sendFrameSync(bufferedFrame);
1697
+ if (sendRet >= 0 || sendRet === AVERROR_EAGAIN) {
1698
+ break;
1699
+ }
1700
+ }
1701
+ catch (e_10) {
1702
+ env_10.error = e_10;
1703
+ env_10.hasError = true;
1704
+ }
1705
+ finally {
1706
+ __disposeResources(env_10);
1707
+ }
1708
+ }
1709
+ const ret = this.codecContext.receivePacketSync(this.packet);
1710
+ if (ret === 0) {
1711
+ // Set packet timebase to codec timebase
1712
+ this.packet.timeBase = this.codecContext.timeBase;
1713
+ // Mark packet as trusted (from encoder)
1714
+ this.packet.setFlags(AV_PKT_FLAG_TRUSTED);
1715
+ // Got a packet, clone it for the user
1716
+ const cloned = this.packet.clone();
1717
+ if (!cloned) {
1718
+ throw new Error('Failed to clone packet (out of memory)');
1719
+ }
1720
+ return cloned;
1721
+ }
1722
+ else if (ret === AVERROR_EAGAIN) {
1723
+ // Need more data
1724
+ return null;
1725
+ }
1726
+ else if (ret === AVERROR_EOF) {
1727
+ // End of stream
1728
+ return EOF;
1729
+ }
1730
+ else {
1731
+ // Error
1732
+ FFmpegError.throwIfError(ret, 'Failed to receive packet');
1733
+ return null;
1734
+ }
1735
+ }
1736
+ /**
1737
+ * Pipe encoded packets to muxer.
1738
+ *
1739
+ * @param target - Media output component to write packets to
1740
+ *
1741
+ * @param streamIndex - Stream index to write packets to
1742
+ *
1743
+ * @returns Scheduler for continued chaining
1744
+ *
1745
+ * @example
1746
+ * ```typescript
1747
+ * decoder.pipeTo(filter).pipeTo(encoder)
1748
+ * ```
1749
+ */
1750
+ pipeTo(target, streamIndex) {
1751
+ // Start worker if not already running
1752
+ this.workerPromise ??= this.runWorker();
1753
+ // Start pipe task: encoder.outputQueue -> output
1754
+ this.pipeToPromise = (async () => {
1755
+ while (true) {
1756
+ const packet = await this.receiveFromQueue();
1757
+ if (!packet)
1758
+ break;
1759
+ try {
1760
+ // writePacket clones internally; the queue-owned packet is ours to free
1761
+ await target.writePacket(packet, streamIndex);
1762
+ }
1763
+ finally {
1764
+ packet.free();
1765
+ }
1766
+ }
1767
+ })();
1768
+ // Return control without pipeTo (terminal stage)
1769
+ return new SchedulerControl(this);
1770
+ }
1771
+ /**
1772
+ * Close encoder and free resources.
1773
+ *
1774
+ * Releases codec context and internal packet buffer.
1775
+ * Safe to call multiple times.
1776
+ * Automatically called by Symbol.dispose.
1777
+ *
1778
+ * @example
1779
+ * ```typescript
1780
+ * const encoder = await Encoder.create(FF_ENCODER_LIBX264, { ... });
1781
+ * try {
1782
+ * // Use encoder
1783
+ * } finally {
1784
+ * encoder.close();
1785
+ * }
1786
+ * ```
1787
+ *
1788
+ * @see {@link Symbol.dispose} For automatic cleanup
1789
+ */
1790
+ close() {
1791
+ if (this.isClosed) {
1792
+ return;
1793
+ }
1794
+ this.isClosed = true;
1795
+ this.inputQueue.close();
1796
+ this.outputQueue.close();
1797
+ this.inputQueue.clear();
1798
+ this.outputQueue.clear();
1799
+ this.audioFrameBuffer?.[Symbol.dispose]();
1800
+ this.audioFrameBuffer = undefined;
1801
+ this.audioResampler?.[Symbol.dispose]();
1802
+ this.audioResampler = undefined;
1803
+ this.resampledFrame?.free();
1804
+ this.resampledFrame = undefined;
1805
+ this.videoScaler?.[Symbol.dispose]();
1806
+ this.videoScaler = undefined;
1807
+ this.scaledFrame?.free();
1808
+ this.scaledFrame = undefined;
1809
+ this.codecContext.freeContext();
1810
+ this.packet.free();
1811
+ this.initialized = false;
1812
+ }
1813
+ /**
1814
+ * Get encoder codec.
1815
+ *
1816
+ * Returns the codec used by this encoder.
1817
+ * Useful for checking codec capabilities and properties.
1818
+ *
1819
+ * @returns Codec instance
1820
+ *
1821
+ * @internal
1822
+ *
1823
+ * @see {@link Codec} For codec details
1824
+ */
1825
+ getCodec() {
1826
+ return this.codec;
1827
+ }
1828
+ /**
1829
+ * Get underlying codec context.
1830
+ *
1831
+ * Returns the codec context for advanced operations.
1832
+ * Useful for accessing low-level codec properties and settings.
1833
+ * Returns null if encoder is closed or not initialized.
1834
+ *
1835
+ * @returns Codec context or null if closed/not initialized
1836
+ *
1837
+ * @internal
1838
+ *
1839
+ * @see {@link CodecContext} For context details
1840
+ */
1841
+ getCodecContext() {
1842
+ return !this.isClosed && this.initialized ? this.codecContext : null;
1843
+ }
1844
+ /**
1845
+ * Worker loop for push-based processing.
1846
+ *
1847
+ * @internal
1848
+ */
1849
+ async runWorker() {
1850
+ try {
1851
+ // Outer loop - receive frames
1852
+ while (!this.inputQueue.isClosed) {
1853
+ const env_11 = { stack: [], error: void 0, hasError: false };
1854
+ try {
1855
+ const frame = __addDisposableResource(env_11, await this.inputQueue.receive(), false);
1856
+ if (!frame)
1857
+ break;
1858
+ // Open encoder if not already done
1859
+ if (!this.initialized) {
1860
+ this.initializePromise ??= this.initialize(frame);
1861
+ }
1862
+ await this.initializePromise;
1863
+ await this.encode(frame);
1864
+ // Receive packets
1865
+ while (!this.outputQueue.isClosed) {
1866
+ const packet = await this.receive();
1867
+ if (!packet)
1868
+ break; // Stop on EAGAIN or EOF
1869
+ await this.outputQueue.send(packet); // Only send actual packets
1870
+ }
1871
+ }
1872
+ catch (e_11) {
1873
+ env_11.error = e_11;
1874
+ env_11.hasError = true;
1875
+ }
1876
+ finally {
1877
+ __disposeResources(env_11);
1878
+ }
1879
+ }
1880
+ // Flush encoder at end
1881
+ await this.flush();
1882
+ while (!this.outputQueue.isClosed) {
1883
+ const packet = await this.receive();
1884
+ if (!packet)
1885
+ break; // Stop on EAGAIN or EOF
1886
+ await this.outputQueue.send(packet); // Only send actual packets
1887
+ }
1888
+ }
1889
+ catch (error) {
1890
+ // Propagate error to both queues so upstream and downstream know
1891
+ const err = error instanceof Error ? error : new Error(String(error));
1892
+ this.inputQueue?.closeWithError(err);
1893
+ this.outputQueue?.closeWithError(err);
1894
+ }
1895
+ finally {
1896
+ // Close output queue when done (if not already closed with error)
1897
+ this.outputQueue?.close();
1898
+ }
1899
+ }
1900
+ /**
1901
+ * Send frame to input queue or flush the pipeline.
1902
+ *
1903
+ * When frame is provided, queues it for encoding.
1904
+ * When null is provided, triggers flush sequence:
1905
+ * - Closes input queue
1906
+ * - Waits for worker completion
1907
+ * - Flushes encoder and sends remaining packets to output queue
1908
+ * - Closes output queue
1909
+ * - Waits for pipeTo task completion (writes to muxer)
1910
+ *
1911
+ * Used by scheduler system for pipeline control.
1912
+ *
1913
+ * @param frame - Frame to send, or null to flush
1914
+ *
1915
+ * @internal
1916
+ */
1917
+ async sendToQueue(frame) {
1918
+ if (frame) {
1919
+ await this.inputQueue.send(frame);
1920
+ }
1921
+ else {
1922
+ // Close input queue to signal end of stream to worker
1923
+ this.inputQueue.close();
1924
+ // Wait for worker to finish processing all frames (if exists)
1925
+ if (this.workerPromise) {
1926
+ await this.workerPromise;
1927
+ }
1928
+ // Flush encoder at end
1929
+ await this.flush();
1930
+ while (true) {
1931
+ const packet = await this.receive();
1932
+ if (!packet)
1933
+ break; // Stop on EAGAIN or EOF
1934
+ await this.outputQueue.send(packet); // Only send actual packets
1935
+ }
1936
+ if (this.pipeToPromise) {
1937
+ await this.pipeToPromise;
1938
+ }
1939
+ }
1940
+ }
1941
+ /**
1942
+ * Receive packet from output queue.
1943
+ *
1944
+ * @returns Packet from output queue
1945
+ *
1946
+ * @internal
1947
+ */
1948
+ async receiveFromQueue() {
1949
+ return await this.outputQueue.receive();
1950
+ }
1951
+ /**
1952
+ * Apply the user-supplied {@link EncoderOptions.context} bag to the codec context.
1953
+ *
1954
+ * Normalizes the rate-control fields (which accept `'5M'`-style strings) before
1955
+ * assigning every provided field via its setter.
1956
+ *
1957
+ * @internal
1958
+ */
1959
+ applyContextBag() {
1960
+ const context = this.options.context;
1961
+ if (!context) {
1962
+ return;
1963
+ }
1964
+ const resolved = { ...context };
1965
+ for (const field of BITRATE_FIELDS) {
1966
+ const value = resolved[field];
1967
+ if (value !== undefined) {
1968
+ // rcBufferSize is a number; bitRate/rcMinRate/rcMaxRate are bigint.
1969
+ resolved[field] = field === 'rcBufferSize' ? Number(normalizeBitrate(value)) : normalizeBitrate(value);
1970
+ }
1971
+ }
1972
+ applyContextOptions(this.codecContext, resolved);
1973
+ }
1974
+ /**
1975
+ * Set the frame rate and time base of a video encoder from its first frame.
1976
+ *
1977
+ * Shared by both initialize variants.
1978
+ *
1979
+ * @param frame - First frame to encode
1980
+ *
1981
+ * @internal
1982
+ */
1983
+ setupVideoTiming(frame) {
1984
+ // Get framerate from filter if available, otherwise from decoder
1985
+ // This matches FFmpeg CLI behavior where encoder gets frame_rate_filter from FrameData
1986
+ const filterFrameRate = this.options.filter?.frameRate;
1987
+ if (filterFrameRate) {
1988
+ this.codecContext.framerate = new Rational(filterFrameRate.num, filterFrameRate.den);
1989
+ }
1990
+ // If no filter framerate, try to get from decoder stream
1991
+ if ((!this.codecContext.framerate || this.codecContext.framerate.num === 0) && this.options.decoder) {
1992
+ const decoderCtx = this.options.decoder.getCodecContext();
1993
+ if (decoderCtx?.framerate && decoderCtx.framerate.num > 0) {
1994
+ this.codecContext.framerate = decoderCtx.framerate;
1995
+ }
1996
+ else {
1997
+ // The bitstream carries no rate (e.g. MJPEG, or an H.264 camera without
1998
+ // VUI timing): guess it from the stream like FFmpeg CLI does. A stream
1999
+ // probed too briefly to see two frames (RTPStream reads with
2000
+ // analyzeduration 0) offers only its clock as rate (e.g. 90000/1).
2001
+ const guessed = this.options.decoder.getGuessedFrameRate();
2002
+ if (isFrameRate(guessed)) {
2003
+ this.codecContext.framerate = new Rational(guessed.num, guessed.den);
2004
+ }
2005
+ }
2006
+ }
2007
+ // FFmpeg CLI sets encoder time_base to 1/framerate (inverse of framerate)
2008
+ // This allows encoder to produce sequential PTS (0, 1, 2, 3...) which enables
2009
+ // proper B-frame DTS generation (negative DTS values)
2010
+ if (this.codecContext.framerate && this.codecContext.framerate.num > 0) {
2011
+ // Use inverse of framerate (e.g., framerate=30/1 → timebase=1/30)
2012
+ this.codecContext.timeBase = new Rational(this.codecContext.framerate.den, this.codecContext.framerate.num);
2013
+ return;
2014
+ }
2015
+ // Fallback: use frame timebase if framerate not available
2016
+ this.codecContext.timeBase = frame.timeBase;
2017
+ // Without a framerate encoders read the inverse time base as one: for a
2018
+ // 90 kHz clock x264 picks level 6.2 and VAAPI and QSV fail to encode.
2019
+ // Declare FFmpeg CLI's default rate and keep the clock, so the timestamps
2020
+ // stay apart whatever the real rate is.
2021
+ if (!isFrameRate({ num: frame.timeBase.den, den: frame.timeBase.num })) {
2022
+ Log.log(AV_LOG_WARNING, `No information about the input frame rate is available, encoding as ${FALLBACK_FRAME_RATE} fps\n`);
2023
+ this.codecContext.framerate = new Rational(FALLBACK_FRAME_RATE, 1);
2024
+ }
2025
+ }
2026
+ /**
2027
+ * Initialize encoder from first frame.
2028
+ *
2029
+ * Sets codec context parameters from frame properties.
2030
+ * Configures hardware context if present in frame.
2031
+ * Opens encoder with accumulated options.
2032
+ *
2033
+ * @param frame - First frame to encode
2034
+ *
2035
+ * @throws {FFmpegError} If encoder open fails
2036
+ *
2037
+ * @internal
2038
+ */
2039
+ async initialize(frame) {
2040
+ // Get bits_per_raw_sample from decoder if available
2041
+ if (this.options.decoder) {
2042
+ const decoderCtx = this.options.decoder.getCodecContext();
2043
+ if (decoderCtx && decoderCtx.bitsPerRawSample > 0) {
2044
+ this.codecContext.bitsPerRawSample = decoderCtx.bitsPerRawSample;
2045
+ }
2046
+ }
2047
+ if (frame.isVideo()) {
2048
+ this.setupVideoTiming(frame);
2049
+ this.codecContext.width = frame.width;
2050
+ this.codecContext.height = frame.height;
2051
+ // Pick a codec-supported pixel format (converting on demand when autoFormat).
2052
+ this.setupVideoFormat(frame);
2053
+ this.codecContext.sampleAspectRatio = frame.sampleAspectRatio;
2054
+ this.codecContext.colorRange = frame.colorRange;
2055
+ this.codecContext.colorPrimaries = frame.colorPrimaries;
2056
+ this.codecContext.colorTrc = frame.colorTrc;
2057
+ this.codecContext.colorSpace = frame.colorSpace;
2058
+ // Only set chroma location if unspecified
2059
+ if (this.codecContext.chromaLocation === AVCHROMA_LOC_UNSPECIFIED) {
2060
+ this.codecContext.chromaLocation = frame.chromaLocation;
2061
+ }
2062
+ }
2063
+ else {
2064
+ // Audio: pick codec-supported sample rate/format/layout (resampling on demand).
2065
+ this.setupAudioParams(frame);
2066
+ }
2067
+ // Setup hardware acceleration with validation
2068
+ this.setupHardwareAcceleration(frame);
2069
+ // AV_CODEC_FLAG_COPY_OPAQUE: Copy opaque data from frames to packets if supported
2070
+ if (this.codec.hasCapabilities(AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE)) {
2071
+ this.codecContext.setFlags(AV_CODEC_FLAG_COPY_OPAQUE);
2072
+ }
2073
+ // AV_CODEC_FLAG_FRAME_DURATION: Signal that frame duration matters for timestamps
2074
+ this.codecContext.setFlags(AV_CODEC_FLAG_FRAME_DURATION);
2075
+ this.applyContextBag();
2076
+ this.options.configure?.(this.codecContext);
2077
+ // Open codec
2078
+ const openRet = await this.codecContext.open2(this.codec, this.opts);
2079
+ if (openRet < 0) {
2080
+ this.codecContext.freeContext();
2081
+ FFmpegError.throwIfError(openRet, 'Failed to open encoder');
2082
+ }
2083
+ // Check if encoder requires fixed frame size (e.g., Opus, AAC, MP3)
2084
+ // If so, create AudioFrameBuffer to automatically chunk frames
2085
+ if (frame.isAudio() && this.codecContext.frameSize > 0) {
2086
+ this.audioFrameBuffer = AudioFrameBuffer.create(this.codecContext.frameSize, this.codecContext.sampleFormat, this.codecContext.sampleRate, this.codecContext.channelLayout, this.codecContext.channels, this.options.audioFrameBuffer);
2087
+ }
2088
+ this.initialized = true;
2089
+ }
2090
+ /**
2091
+ * Initialize encoder from first frame synchronously.
2092
+ * Synchronous version of initialize.
2093
+ *
2094
+ * Sets codec context parameters from frame properties.
2095
+ * Configures hardware context if present in frame.
2096
+ * Opens encoder with accumulated options.
2097
+ *
2098
+ * @param frame - First frame to encode
2099
+ *
2100
+ * @throws {FFmpegError} If encoder open fails
2101
+ *
2102
+ * @internal
2103
+ *
2104
+ * @see {@link initialize} For async version
2105
+ */
2106
+ initializeSync(frame) {
2107
+ // Get bits_per_raw_sample from decoder if available
2108
+ if (this.options.decoder) {
2109
+ const decoderCtx = this.options.decoder.getCodecContext();
2110
+ if (decoderCtx && decoderCtx.bitsPerRawSample > 0) {
2111
+ this.codecContext.bitsPerRawSample = decoderCtx.bitsPerRawSample;
2112
+ }
2113
+ }
2114
+ if (frame.isVideo()) {
2115
+ this.setupVideoTiming(frame);
2116
+ this.codecContext.width = frame.width;
2117
+ this.codecContext.height = frame.height;
2118
+ // Pick a codec-supported pixel format (converting on demand when autoFormat).
2119
+ this.setupVideoFormat(frame);
2120
+ this.codecContext.sampleAspectRatio = frame.sampleAspectRatio;
2121
+ this.codecContext.colorRange = frame.colorRange;
2122
+ this.codecContext.colorPrimaries = frame.colorPrimaries;
2123
+ this.codecContext.colorTrc = frame.colorTrc;
2124
+ this.codecContext.colorSpace = frame.colorSpace;
2125
+ // Only set chroma location if unspecified
2126
+ if (this.codecContext.chromaLocation === AVCHROMA_LOC_UNSPECIFIED) {
2127
+ this.codecContext.chromaLocation = frame.chromaLocation;
2128
+ }
2129
+ }
2130
+ else {
2131
+ // Audio: pick codec-supported sample rate/format/layout (resampling on demand).
2132
+ this.setupAudioParams(frame);
2133
+ }
2134
+ // Setup hardware acceleration with validation
2135
+ this.setupHardwareAcceleration(frame);
2136
+ // Set codec flags
2137
+ // AV_CODEC_FLAG_COPY_OPAQUE: Copy opaque data from frames to packets if supported
2138
+ if (this.codec.hasCapabilities(AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE)) {
2139
+ this.codecContext.setFlags(AV_CODEC_FLAG_COPY_OPAQUE);
2140
+ }
2141
+ // AV_CODEC_FLAG_FRAME_DURATION: Signal that frame duration matters for timestamps
2142
+ this.codecContext.setFlags(AV_CODEC_FLAG_FRAME_DURATION);
2143
+ this.applyContextBag();
2144
+ this.options.configure?.(this.codecContext);
2145
+ // Open codec
2146
+ const openRet = this.codecContext.open2Sync(this.codec, this.opts);
2147
+ if (openRet < 0) {
2148
+ this.codecContext.freeContext();
2149
+ FFmpegError.throwIfError(openRet, 'Failed to open encoder');
2150
+ }
2151
+ // Check if encoder requires fixed frame size (e.g., Opus, AAC, MP3)
2152
+ // If so, create AudioFrameBuffer to automatically chunk frames
2153
+ if (frame.isAudio() && this.codecContext.frameSize > 0) {
2154
+ this.audioFrameBuffer = AudioFrameBuffer.create(this.codecContext.frameSize, this.codecContext.sampleFormat, this.codecContext.sampleRate, this.codecContext.channelLayout, this.codecContext.channels, this.options.audioFrameBuffer);
2155
+ }
2156
+ this.initialized = true;
2157
+ }
2158
+ /**
2159
+ * Setup hardware acceleration for encoder.
2160
+ *
2161
+ * Implements FFmpeg's hw_device_setup_for_encode logic.
2162
+ * Validates hardware frames context format and codec support.
2163
+ * Falls back to device context if frames context is incompatible.
2164
+ *
2165
+ * @param frame - Frame to get hardware context from
2166
+ *
2167
+ * @internal
2168
+ */
2169
+ setupHardwareAcceleration(frame) {
2170
+ if (!frame.hwFramesCtx) {
2171
+ // Software encoding
2172
+ return;
2173
+ }
2174
+ const hwFramesCtx = frame.hwFramesCtx;
2175
+ const framesFormat = hwFramesCtx.format;
2176
+ const encoderFormat = this.codecContext.pixelFormat;
2177
+ // Check 1: Format validation
2178
+ if (framesFormat !== encoderFormat) {
2179
+ this.codecContext.hwDeviceCtx = hwFramesCtx.deviceRef;
2180
+ this.codecContext.hwFramesCtx = null;
2181
+ return;
2182
+ }
2183
+ // Check 2: Codec supports HW_FRAMES_CTX?
2184
+ let supportsFramesCtx = false;
2185
+ for (let i = 0;; i++) {
2186
+ const config = this.codec.getHwConfig(i);
2187
+ if (!config)
2188
+ break;
2189
+ // Check if codec supports HW_FRAMES_CTX method
2190
+ if (config.methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) {
2191
+ // Check if pixel format matches or is unspecified
2192
+ if (config.pixFmt === AV_PIX_FMT_NONE || config.pixFmt === encoderFormat) {
2193
+ supportsFramesCtx = true;
2194
+ break;
2195
+ }
2196
+ }
2197
+ }
2198
+ if (supportsFramesCtx) {
2199
+ // Use hw_frames_ctx (best performance - zero copy)
2200
+ this.codecContext.hwFramesCtx = hwFramesCtx;
2201
+ this.codecContext.hwDeviceCtx = hwFramesCtx.deviceRef;
2202
+ }
2203
+ else {
2204
+ // Fallback to hw_device_ctx (still uses HW, but may copy)
2205
+ // Check if codec supports HW_DEVICE_CTX as fallback
2206
+ let supportsDeviceCtx = false;
2207
+ for (let i = 0;; i++) {
2208
+ const config = this.codec.getHwConfig(i);
2209
+ if (!config)
2210
+ break;
2211
+ if (config.methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) {
2212
+ supportsDeviceCtx = true;
2213
+ break;
2214
+ }
2215
+ }
2216
+ if (supportsDeviceCtx) {
2217
+ this.codecContext.hwDeviceCtx = hwFramesCtx.deviceRef;
2218
+ this.codecContext.hwFramesCtx = null;
2219
+ }
2220
+ else {
2221
+ // No hardware support at all - software encoding
2222
+ this.codecContext.hwDeviceCtx = null;
2223
+ this.codecContext.hwFramesCtx = null;
2224
+ }
2225
+ }
2226
+ }
2227
+ /**
2228
+ * Configure the codec context's audio parameters from the first frame.
2229
+ *
2230
+ * Audio encoders only accept specific sample rates / sample formats / channel
2231
+ * layouts. This picks codec-supported targets; if they differ from the input it
2232
+ * either sets up a resampler (when `autoResample`) or throws a descriptive error.
2233
+ *
2234
+ * @param frame - First audio frame
2235
+ *
2236
+ * @throws {Error} If the input is unsupported and `autoResample` is disabled
2237
+ *
2238
+ * @throws {FFmpegError} If the resampler fails to configure
2239
+ *
2240
+ * @internal
2241
+ */
2242
+ setupAudioParams(frame) {
2243
+ const inRate = frame.sampleRate;
2244
+ const inFmt = frame.format;
2245
+ // Codec open and swr both need a concrete layout. PCM/raw frames often carry
2246
+ // an unspecified layout (order UNSPEC, mask 0); normalize it to the canonical
2247
+ // native layout and re-apply it to each incoming frame (see encode()) so it
2248
+ // matches the opened codec context / resampler input.
2249
+ let inLayout = frame.channelLayout;
2250
+ if (inLayout.order === AV_CHANNEL_ORDER_UNSPEC) {
2251
+ inLayout = avChannelLayoutDefault(inLayout.nbChannels);
2252
+ this.audioInputLayout = inLayout;
2253
+ }
2254
+ const targetRate = pickSupportedRate(inRate, this.codec.supportedSamplerates);
2255
+ const targetFmt = pickSupportedSampleFormat(inFmt, this.codec.sampleFormats);
2256
+ const targetLayout = pickSupportedLayout(inLayout, this.codec.channelLayouts);
2257
+ // When the codec forces a different sample rate, the resampler stamps its
2258
+ // output in 1/target_rate, so that is the codec timebase. Without rate
2259
+ // conversion, keep the frame timebase (typically 1/sample_rate).
2260
+ this.resampleChangesRate = targetRate !== inRate;
2261
+ this.resampleInputRate = inRate;
2262
+ this.codecContext.timeBase = this.resampleChangesRate ? new Rational(1, targetRate) : frame.timeBase;
2263
+ const needsResample = targetRate !== inRate || targetFmt !== inFmt || targetLayout.nbChannels !== inLayout.nbChannels;
2264
+ if (needsResample && !this.autoResample) {
2265
+ const mismatches = [];
2266
+ if (targetRate !== inRate) {
2267
+ const rates = this.codec.supportedSamplerates;
2268
+ mismatches.push(`sample rate ${inRate} Hz` + (rates && rates.length > 0 ? ` (supported: ${rates.join(', ')})` : ''));
2269
+ }
2270
+ if (targetFmt !== inFmt) {
2271
+ const fmts = this.codec.sampleFormats;
2272
+ const supported = fmts && fmts.length > 0 ? ` (supported: ${fmts.map((f) => avGetSampleFmtName(f) ?? f).join(', ')})` : '';
2273
+ mismatches.push(`sample format ${avGetSampleFmtName(inFmt) ?? inFmt}${supported}`);
2274
+ }
2275
+ if (targetLayout.nbChannels !== inLayout.nbChannels) {
2276
+ mismatches.push(`channel count ${inLayout.nbChannels}ch`);
2277
+ }
2278
+ throw new Error(`Encoder '${this.codec.name}' cannot encode the input audio ` +
2279
+ `(${inRate} Hz, ${avGetSampleFmtName(inFmt) ?? inFmt}, ${inLayout.nbChannels}ch): ` +
2280
+ `incompatible ${mismatches.join('; ')}. ` +
2281
+ 'Set { autoResample: true } on the encoder, or convert the input with an aresample/aformat filter first.');
2282
+ }
2283
+ this.codecContext.sampleRate = targetRate;
2284
+ this.codecContext.sampleFormat = targetFmt;
2285
+ this.codecContext.channelLayout = targetLayout;
2286
+ if (needsResample) {
2287
+ const swr = new SoftwareResampleContext();
2288
+ FFmpegError.throwIfError(swr.allocSetOpts2(targetLayout, targetFmt, targetRate, inLayout, inFmt, inRate), 'Failed to configure audio resampler');
2289
+ FFmpegError.throwIfError(swr.init(), 'Failed to initialize audio resampler');
2290
+ this.audioResampler = swr;
2291
+ }
2292
+ }
2293
+ /**
2294
+ * Lazily allocate the reused resampler output frame.
2295
+ *
2296
+ * @returns The allocated output frame
2297
+ *
2298
+ * @internal
2299
+ */
2300
+ getResampleFrame() {
2301
+ if (!this.resampledFrame) {
2302
+ this.resampledFrame = new Frame();
2303
+ this.resampledFrame.alloc();
2304
+ }
2305
+ return this.resampledFrame;
2306
+ }
2307
+ /**
2308
+ * Resample an incoming audio frame to the codec's target format.
2309
+ *
2310
+ * Reuses a single output frame; `swr_convert_frame` allocates/sizes its buffer.
2311
+ * The output keeps the input timing: with rate conversion it is stamped with the
2312
+ * time of its first output sample in 1/target_rate, otherwise it carries the input
2313
+ * frame's PTS and timebase.
2314
+ *
2315
+ * @param frame - Source audio frame
2316
+ *
2317
+ * @returns The resampled frame (owned by the encoder, reused across calls)
2318
+ *
2319
+ * @throws {FFmpegError} If resampling fails
2320
+ *
2321
+ * @internal
2322
+ */
2323
+ resampleAudio(frame) {
2324
+ const out = this.getResampleFrame();
2325
+ out.unref();
2326
+ out.format = this.codecContext.sampleFormat;
2327
+ out.sampleRate = this.codecContext.sampleRate;
2328
+ out.channelLayout = this.codecContext.channelLayout;
2329
+ // Rate conversion holds samples back inside swr, so this call's first output
2330
+ // sample is older than the input frame's first sample. swr_next_pts() accounts
2331
+ // for that delay (as the aresample filter does) and must run before converting.
2332
+ const pts = this.resampleChangesRate ? this.nextResampledPts(frame) : AV_NOPTS_VALUE;
2333
+ FFmpegError.throwIfError(this.audioResampler.convertFrame(out, frame), 'Failed to resample audio frame');
2334
+ if (this.resampleChangesRate) {
2335
+ out.timeBase = this.codecContext.timeBase;
2336
+ out.pts = pts;
2337
+ }
2338
+ else {
2339
+ out.timeBase = frame.timeBase;
2340
+ out.pts = frame.pts;
2341
+ }
2342
+ return out;
2343
+ }
2344
+ /**
2345
+ * Drain samples buffered inside the resampler (rate-conversion delay) into the
2346
+ * encoder path. Returns the drained frame if any, else null.
2347
+ *
2348
+ * @returns The drained frame (reused), or null when the resampler is empty
2349
+ *
2350
+ * @internal
2351
+ */
2352
+ drainResampler() {
2353
+ if (!this.audioResampler) {
2354
+ return null;
2355
+ }
2356
+ const out = this.getResampleFrame();
2357
+ out.unref();
2358
+ out.format = this.codecContext.sampleFormat;
2359
+ out.sampleRate = this.codecContext.sampleRate;
2360
+ out.channelLayout = this.codecContext.channelLayout;
2361
+ const pts = this.resampleChangesRate ? this.nextResampledPts(null) : AV_NOPTS_VALUE;
2362
+ const ret = this.audioResampler.convertFrame(out, null);
2363
+ if (ret < 0 || out.nbSamples <= 0) {
2364
+ return null;
2365
+ }
2366
+ if (this.resampleChangesRate) {
2367
+ out.timeBase = this.codecContext.timeBase;
2368
+ out.pts = pts;
2369
+ }
2370
+ return out;
2371
+ }
2372
+ /**
2373
+ * Timestamp of the resampler's next output sample in 1/target_rate.
2374
+ *
2375
+ * Mirrors the aresample filter: the input PTS goes to swr_next_pts() in
2376
+ * 1/(in_rate * out_rate), which subtracts the samples still buffered in the
2377
+ * resampler. Without an input PTS (or when draining) swr continues from the
2378
+ * samples it has output so far.
2379
+ *
2380
+ * @param frame - Input frame about to be converted, or null when draining
2381
+ *
2382
+ * @returns Output timestamp in 1/target_rate
2383
+ *
2384
+ * @internal
2385
+ */
2386
+ nextResampledPts(frame) {
2387
+ const inRate = BigInt(this.resampleInputRate);
2388
+ const outRate = BigInt(this.codecContext.sampleRate);
2389
+ let inPts = AV_NOPTS_VALUE;
2390
+ if (frame && frame.pts !== AV_NOPTS_VALUE) {
2391
+ const tb = frame.timeBase;
2392
+ if (tb.num > 0 && tb.den > 0) {
2393
+ inPts = avRescaleRnd(frame.pts, BigInt(tb.num) * inRate * outRate, BigInt(tb.den), AV_ROUND_NEAR_INF);
2394
+ }
2395
+ }
2396
+ // Rounded division back to 1/out_rate (ROUNDED_DIV in af_aresample)
2397
+ const outPts = this.audioResampler.nextPts(inPts);
2398
+ const half = inRate / 2n;
2399
+ return (outPts >= 0n ? outPts + half : outPts - half) / inRate;
2400
+ }
2401
+ /**
2402
+ * Configure the codec context's pixel format from the first video frame.
2403
+ *
2404
+ * Video encoders only accept specific pixel formats. This keeps the input format
2405
+ * when the codec accepts it; otherwise it either sets up a swscale converter to
2406
+ * the least-loss supported format (when `autoFormat`) or throws a descriptive
2407
+ * error. Hardware frames are left untouched - their format is negotiated through
2408
+ * the hardware frames context, not swscale.
2409
+ *
2410
+ * @param frame - First video frame
2411
+ *
2412
+ * @throws {Error} If the input is unsupported and `autoFormat` is disabled
2413
+ *
2414
+ * @throws {FFmpegError} If the converter fails to configure
2415
+ *
2416
+ * @internal
2417
+ */
2418
+ setupVideoFormat(frame) {
2419
+ const inFmt = frame.format;
2420
+ // Hardware frames carry a hw pixfmt negotiated via hw_frames_ctx; swscale can't
2421
+ // touch them - leave the format untouched.
2422
+ if (frame.isHwFrame()) {
2423
+ this.codecContext.pixelFormat = inFmt;
2424
+ return;
2425
+ }
2426
+ const targetFmt = pickSupportedPixelFormat(inFmt, this.codec.pixelFormats);
2427
+ const needsConversion = targetFmt !== inFmt;
2428
+ if (needsConversion && !this.autoFormat) {
2429
+ const supported = this.codec.pixelFormats;
2430
+ throw new Error(`Encoder '${this.codec.name}' does not support the input pixel format ` +
2431
+ `(${avGetPixFmtName(inFmt) ?? inFmt}). Supported: ${supported.map((f) => avGetPixFmtName(f) ?? f).join(', ')}` +
2432
+ '. Set { autoFormat: true } on the encoder, or convert the input with a scale/format filter first.');
2433
+ }
2434
+ this.codecContext.pixelFormat = targetFmt;
2435
+ // Set up a same-size swscale converter when the codec needs a different format.
2436
+ if (needsConversion) {
2437
+ this.videoTargetFormat = targetFmt;
2438
+ const sws = new SoftwareScaleContext();
2439
+ sws.getContext(frame.width, frame.height, inFmt, frame.width, frame.height, targetFmt, SWS_BILINEAR);
2440
+ FFmpegError.throwIfError(sws.initContext(), 'Failed to configure pixel-format converter');
2441
+ this.videoScaler = sws;
2442
+ }
2443
+ }
2444
+ /**
2445
+ * Lazily allocate the reused scaler output frame.
2446
+ *
2447
+ * @returns The allocated output frame
2448
+ *
2449
+ * @internal
2450
+ */
2451
+ getScaledFrame() {
2452
+ if (!this.scaledFrame) {
2453
+ this.scaledFrame = new Frame();
2454
+ this.scaledFrame.alloc();
2455
+ }
2456
+ return this.scaledFrame;
2457
+ }
2458
+ /**
2459
+ * Convert an incoming video frame to the codec's target pixel format.
2460
+ *
2461
+ * Reuses a single output frame; `sws_scale_frame` allocates/sizes its buffer.
2462
+ * Resolution is unchanged - only the pixel format differs. Timing is carried over
2463
+ * explicitly so the encoder's PTS rescale stays correct.
2464
+ *
2465
+ * @param frame - Source video frame
2466
+ *
2467
+ * @returns The converted frame (owned by the encoder, reused across calls)
2468
+ *
2469
+ * @internal
2470
+ */
2471
+ scaleVideo(frame) {
2472
+ const out = this.getScaledFrame();
2473
+ out.unref();
2474
+ out.format = this.videoTargetFormat;
2475
+ out.width = frame.width;
2476
+ out.height = frame.height;
2477
+ FFmpegError.throwIfError(this.videoScaler.scaleFrameSync(out, frame), 'Failed to convert video frame format');
2478
+ out.timeBase = frame.timeBase;
2479
+ out.pts = frame.pts;
2480
+ out.duration = frame.duration;
2481
+ return out;
2482
+ }
2483
+ /**
2484
+ * Prepare frame for encoding.
2485
+ *
2486
+ * Implements FFmpeg's frame_encode() pre-encoding logic:
2487
+ * 1. Video: Keeps timestamps strictly increasing like the CLI's VFR sync
2488
+ * 2. Video: Sets frame.quality from encoder's globalQuality (like -qscale)
2489
+ * 3. Audio: Validates channel count consistency for encoders without PARAM_CHANGE capability
2490
+ *
2491
+ * This matches FFmpeg CLI behavior where these properties are automatically managed.
2492
+ *
2493
+ * @param frame - Frame to prepare for encoding
2494
+ *
2495
+ * @returns False if the frame must be dropped
2496
+ *
2497
+ * @throws {Error} If audio channel count changed and encoder doesn't support parameter changes
2498
+ *
2499
+ * @internal
2500
+ */
2501
+ prepareFrameForEncoding(frame) {
2502
+ // Clear pict_type - encoder will determine frame types based on its own settings.
2503
+ // Input stream's frame type hints are irrelevant when re-encoding. Opt out with
2504
+ // { preserveFramePictType: true } to force keyframes via frame.pictType = I.
2505
+ if (!this.options.preserveFramePictType) {
2506
+ frame.pictType = AV_PICTURE_TYPE_NONE;
2507
+ }
2508
+ if (this.keyframeRequested) {
2509
+ this.keyframeRequested = false;
2510
+ frame.pictType = AV_PICTURE_TYPE_I;
2511
+ }
2512
+ // Adjust frame PTS and timebase to encoder timebase
2513
+ // This matches FFmpeg's adjust_frame_pts_to_encoder_tb() behavior which:
2514
+ // 1. Converts PTS from frame's timebase to encoder's timebase (av_rescale_q)
2515
+ // 2. Sets frame->time_base = tb_dst (so encoder gets correct timebase)
2516
+ // Note: prepareFrameForEncoding is always called AFTER initialize(),
2517
+ // so codecContext.timeBase is already set correctly:
2518
+ // - Video: 1/framerate (if available)
2519
+ // - Audio: frame.timeBase from first frame (typically 1/sample_rate)
2520
+ const encoderTimebase = this.codecContext.timeBase;
2521
+ const oldTimebase = frame.timeBase;
2522
+ const pts = frame.pts;
2523
+ const duration = frame.duration;
2524
+ // IMPORTANT: Calculate duration BEFORE converting frame timebase
2525
+ // This matches FFmpeg's video_sync_process() which calculates:
2526
+ // duration = frame->duration * av_q2d(frame->time_base) / av_q2d(ofp->tb_out)
2527
+ // We need the OLD timebase to convert duration properly
2528
+ let frameDuration;
2529
+ if (duration && duration > 0n) {
2530
+ // Convert duration from frame timebase to encoder timebase
2531
+ // This ensures encoder gets correct frame duration for timestamps
2532
+ frameDuration = avRescaleQ(duration, oldTimebase, encoderTimebase);
2533
+ }
2534
+ else {
2535
+ // Default to 1 (constant frame rate behavior)
2536
+ // Matches FFmpeg's CFR mode: frame->duration = 1
2537
+ frameDuration = 1n;
2538
+ }
2539
+ // AV_NOPTS_VALUE (INT64_MIN) must pass through untouched - rescaling it
2540
+ // yields a garbage timestamp instead of the "unset" marker.
2541
+ if (pts !== AV_NOPTS_VALUE) {
2542
+ // Convert PTS to encoder timebase
2543
+ frame.pts = avRescaleQ(pts, oldTimebase, encoderTimebase);
2544
+ }
2545
+ // IMPORTANT: Set frame timebase to encoder timebase
2546
+ // FFmpeg does this in adjust_frame_pts_to_encoder_tb(): frame->time_base = tb_dst
2547
+ // This ensures encoder gets frames with correct timebase (1/framerate for video, 1/sample_rate for audio)
2548
+ frame.timeBase = encoderTimebase;
2549
+ // Set frame duration in encoder timebase
2550
+ // This matches FFmpeg's video_sync_process() which sets frame->duration
2551
+ // based on vsync_method (CFR: 1, VFR: calculated, PASSTHROUGH: calculated)
2552
+ // Since we don't have automatic filter like FFmpeg, we always set it here
2553
+ frame.duration = frameDuration;
2554
+ const codecType = this.codecContext.codecType;
2555
+ if (codecType === AVMEDIA_TYPE_VIDEO) {
2556
+ // Encoders need strictly increasing pts, but frames arriving in a burst can
2557
+ // round onto the same tick of the encoder time base (e.g. 1/25). Like FFmpeg
2558
+ // CLI's VFR sync (video_sync_process): a frame that still reaches the next
2559
+ // free tick moves there, one that ends before it is dropped.
2560
+ if (frame.pts !== AV_NOPTS_VALUE) {
2561
+ const nextPts = this.nextVideoPts;
2562
+ if (nextPts !== undefined && frame.pts < nextPts) {
2563
+ if (frame.pts + frameDuration < nextPts) {
2564
+ // A requested keyframe goes to the next frame instead, as in the CLI
2565
+ this.droppedKeyframe ||= frame.pictType === AV_PICTURE_TYPE_I;
2566
+ return false;
2567
+ }
2568
+ frame.pts = nextPts;
2569
+ }
2570
+ this.nextVideoPts = frame.pts + 1n;
2571
+ }
2572
+ if (this.droppedKeyframe) {
2573
+ frame.pictType = AV_PICTURE_TYPE_I;
2574
+ this.droppedKeyframe = false;
2575
+ }
2576
+ // Video: Set frame quality from encoder's global quality
2577
+ // Only set if encoder has globalQuality configured and frame doesn't already have quality set
2578
+ const globalQuality = this.codecContext.globalQuality;
2579
+ if (globalQuality > 0 && frame.quality <= 0) {
2580
+ frame.quality = globalQuality;
2581
+ }
2582
+ }
2583
+ else if (codecType === AVMEDIA_TYPE_AUDIO) {
2584
+ // Audio: Validate channel count consistency
2585
+ // If encoder doesn't support AV_CODEC_CAP_PARAM_CHANGE, channel count must remain
2586
+ // constant. The capability and the encoder's channel count are stable after open,
2587
+ // so resolve them once instead of per frame.
2588
+ this.supportsParamChange ??= this.codec.hasCapabilities(AV_CODEC_CAP_PARAM_CHANGE);
2589
+ if (!this.supportsParamChange) {
2590
+ this.encoderChannels ??= this.codecContext.channelLayout.nbChannels;
2591
+ const frameChannels = frame.channelLayout?.nbChannels ?? 0;
2592
+ if (this.encoderChannels !== frameChannels) {
2593
+ throw new Error(`Audio channel count changed (${this.encoderChannels} -> ${frameChannels}) and encoder '${this.codec.name}' does not support parameter changes`);
2594
+ }
2595
+ }
2596
+ }
2597
+ return true;
2598
+ }
2599
+ /**
2600
+ * Dispose of encoder.
2601
+ *
2602
+ * Implements Disposable interface for automatic cleanup.
2603
+ * Equivalent to calling close().
2604
+ *
2605
+ * @example
2606
+ * ```typescript
2607
+ * {
2608
+ * using encoder = await Encoder.create(FF_ENCODER_LIBX264, { ... });
2609
+ * // Encode frames...
2610
+ * } // Automatically closed
2611
+ * ```
2612
+ *
2613
+ * @see {@link close} For manual cleanup
2614
+ */
2615
+ [Symbol.dispose]() {
2616
+ this.close();
2617
+ }
2618
+ }
2619
+ //# sourceMappingURL=encoder.js.map