node-av 1.0.3 → 1.2.0

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 (162) hide show
  1. package/README.md +56 -41
  2. package/dist/api/bitstream-filter.d.ts +180 -123
  3. package/dist/api/bitstream-filter.js +182 -126
  4. package/dist/api/bitstream-filter.js.map +1 -1
  5. package/dist/api/decoder.d.ts +286 -130
  6. package/dist/api/decoder.js +321 -159
  7. package/dist/api/decoder.js.map +1 -1
  8. package/dist/api/encoder.d.ts +254 -158
  9. package/dist/api/encoder.js +326 -298
  10. package/dist/api/encoder.js.map +1 -1
  11. package/dist/api/filter-presets.d.ts +912 -0
  12. package/dist/api/filter-presets.js +1407 -0
  13. package/dist/api/filter-presets.js.map +1 -0
  14. package/dist/api/filter.d.ts +280 -284
  15. package/dist/api/filter.js +435 -509
  16. package/dist/api/filter.js.map +1 -1
  17. package/dist/api/hardware.d.ts +226 -159
  18. package/dist/api/hardware.js +405 -287
  19. package/dist/api/hardware.js.map +1 -1
  20. package/dist/api/index.d.ts +3 -2
  21. package/dist/api/index.js +1 -0
  22. package/dist/api/index.js.map +1 -1
  23. package/dist/api/io-stream.d.ts +65 -61
  24. package/dist/api/io-stream.js +45 -47
  25. package/dist/api/io-stream.js.map +1 -1
  26. package/dist/api/media-input.d.ts +244 -141
  27. package/dist/api/media-input.js +207 -104
  28. package/dist/api/media-input.js.map +1 -1
  29. package/dist/api/media-output.d.ts +206 -128
  30. package/dist/api/media-output.js +212 -129
  31. package/dist/api/media-output.js.map +1 -1
  32. package/dist/api/pipeline.d.ts +168 -38
  33. package/dist/api/pipeline.js +238 -14
  34. package/dist/api/pipeline.js.map +1 -1
  35. package/dist/api/types.d.ts +22 -182
  36. package/dist/api/utilities/audio-sample.d.ts +1 -1
  37. package/dist/api/utilities/image.d.ts +1 -1
  38. package/dist/api/utilities/media-type.d.ts +1 -1
  39. package/dist/api/utilities/pixel-format.d.ts +1 -1
  40. package/dist/api/utilities/sample-format.d.ts +1 -1
  41. package/dist/api/utilities/timestamp.d.ts +1 -1
  42. package/dist/api/utils.d.ts +1 -2
  43. package/dist/api/utils.js +9 -0
  44. package/dist/api/utils.js.map +1 -1
  45. package/dist/{lib → constants}/channel-layouts.d.ts +1 -1
  46. package/dist/constants/channel-layouts.js.map +1 -0
  47. package/dist/{lib → constants}/constants.d.ts +19 -4
  48. package/dist/{lib → constants}/constants.js +15 -1
  49. package/dist/constants/constants.js.map +1 -0
  50. package/dist/constants/decoders.d.ts +609 -0
  51. package/dist/constants/decoders.js +617 -0
  52. package/dist/constants/decoders.js.map +1 -0
  53. package/dist/constants/encoders.d.ts +285 -0
  54. package/dist/constants/encoders.js +298 -0
  55. package/dist/constants/encoders.js.map +1 -0
  56. package/dist/constants/index.d.ts +4 -0
  57. package/dist/constants/index.js +5 -0
  58. package/dist/constants/index.js.map +1 -0
  59. package/dist/index.d.ts +1 -0
  60. package/dist/index.js +2 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/lib/audio-fifo.d.ts +128 -171
  63. package/dist/lib/audio-fifo.js +130 -173
  64. package/dist/lib/audio-fifo.js.map +1 -1
  65. package/dist/lib/binding.d.ts +7 -5
  66. package/dist/lib/binding.js +5 -0
  67. package/dist/lib/binding.js.map +1 -1
  68. package/dist/lib/bitstream-filter-context.d.ts +139 -184
  69. package/dist/lib/bitstream-filter-context.js +139 -188
  70. package/dist/lib/bitstream-filter-context.js.map +1 -1
  71. package/dist/lib/bitstream-filter.d.ts +69 -55
  72. package/dist/lib/bitstream-filter.js +68 -54
  73. package/dist/lib/bitstream-filter.js.map +1 -1
  74. package/dist/lib/codec-context.d.ts +317 -381
  75. package/dist/lib/codec-context.js +316 -381
  76. package/dist/lib/codec-context.js.map +1 -1
  77. package/dist/lib/codec-parameters.d.ts +161 -171
  78. package/dist/lib/codec-parameters.js +162 -172
  79. package/dist/lib/codec-parameters.js.map +1 -1
  80. package/dist/lib/codec-parser.d.ts +92 -105
  81. package/dist/lib/codec-parser.js +92 -103
  82. package/dist/lib/codec-parser.js.map +1 -1
  83. package/dist/lib/codec.d.ts +328 -217
  84. package/dist/lib/codec.js +392 -218
  85. package/dist/lib/codec.js.map +1 -1
  86. package/dist/lib/dictionary.d.ts +150 -204
  87. package/dist/lib/dictionary.js +159 -213
  88. package/dist/lib/dictionary.js.map +1 -1
  89. package/dist/lib/error.d.ts +97 -131
  90. package/dist/lib/error.js +98 -128
  91. package/dist/lib/error.js.map +1 -1
  92. package/dist/lib/filter-context.d.ts +317 -194
  93. package/dist/lib/filter-context.js +335 -200
  94. package/dist/lib/filter-context.js.map +1 -1
  95. package/dist/lib/filter-graph.d.ts +252 -293
  96. package/dist/lib/filter-graph.js +253 -294
  97. package/dist/lib/filter-graph.js.map +1 -1
  98. package/dist/lib/filter-inout.d.ts +87 -95
  99. package/dist/lib/filter-inout.js +87 -95
  100. package/dist/lib/filter-inout.js.map +1 -1
  101. package/dist/lib/filter.d.ts +93 -111
  102. package/dist/lib/filter.js +94 -112
  103. package/dist/lib/filter.js.map +1 -1
  104. package/dist/lib/format-context.d.ts +321 -429
  105. package/dist/lib/format-context.js +314 -386
  106. package/dist/lib/format-context.js.map +1 -1
  107. package/dist/lib/frame.d.ts +263 -406
  108. package/dist/lib/frame.js +263 -408
  109. package/dist/lib/frame.js.map +1 -1
  110. package/dist/lib/hardware-device-context.d.ts +150 -204
  111. package/dist/lib/hardware-device-context.js +149 -203
  112. package/dist/lib/hardware-device-context.js.map +1 -1
  113. package/dist/lib/hardware-frames-context.d.ts +171 -181
  114. package/dist/lib/hardware-frames-context.js +171 -181
  115. package/dist/lib/hardware-frames-context.js.map +1 -1
  116. package/dist/lib/index.d.ts +2 -3
  117. package/dist/lib/index.js +2 -5
  118. package/dist/lib/index.js.map +1 -1
  119. package/dist/lib/input-format.d.ts +90 -118
  120. package/dist/lib/input-format.js +89 -117
  121. package/dist/lib/input-format.js.map +1 -1
  122. package/dist/lib/io-context.d.ts +210 -242
  123. package/dist/lib/io-context.js +221 -253
  124. package/dist/lib/io-context.js.map +1 -1
  125. package/dist/lib/log.d.ts +86 -120
  126. package/dist/lib/log.js +85 -122
  127. package/dist/lib/log.js.map +1 -1
  128. package/dist/lib/native-types.d.ts +127 -112
  129. package/dist/lib/native-types.js +9 -0
  130. package/dist/lib/native-types.js.map +1 -1
  131. package/dist/lib/option.d.ts +285 -242
  132. package/dist/lib/option.js +310 -250
  133. package/dist/lib/option.js.map +1 -1
  134. package/dist/lib/output-format.d.ts +78 -102
  135. package/dist/lib/output-format.js +77 -101
  136. package/dist/lib/output-format.js.map +1 -1
  137. package/dist/lib/packet.d.ts +173 -241
  138. package/dist/lib/packet.js +172 -241
  139. package/dist/lib/packet.js.map +1 -1
  140. package/dist/lib/rational.d.ts +0 -2
  141. package/dist/lib/rational.js +0 -2
  142. package/dist/lib/rational.js.map +1 -1
  143. package/dist/lib/software-resample-context.d.ts +242 -326
  144. package/dist/lib/software-resample-context.js +242 -326
  145. package/dist/lib/software-resample-context.js.map +1 -1
  146. package/dist/lib/software-scale-context.d.ts +130 -174
  147. package/dist/lib/software-scale-context.js +132 -176
  148. package/dist/lib/software-scale-context.js.map +1 -1
  149. package/dist/lib/stream.d.ts +88 -198
  150. package/dist/lib/stream.js +87 -197
  151. package/dist/lib/stream.js.map +1 -1
  152. package/dist/lib/types.d.ts +1 -1
  153. package/dist/lib/utilities.d.ts +372 -181
  154. package/dist/lib/utilities.js +373 -182
  155. package/dist/lib/utilities.js.map +1 -1
  156. package/install/check.js +0 -1
  157. package/package.json +32 -24
  158. package/release_notes.md +43 -13
  159. package/CHANGELOG.md +0 -8
  160. package/dist/lib/channel-layouts.js.map +0 -1
  161. package/dist/lib/constants.js.map +0 -1
  162. /package/dist/{lib → constants}/channel-layouts.js +0 -0
@@ -1,92 +1,78 @@
1
- /**
2
- * Encoder - High-level wrapper for media encoding
3
- *
4
- * Simplifies FFmpeg's encoding API with automatic codec selection,
5
- * parameter configuration, and packet management.
6
- *
7
- * Handles codec initialization, frame encoding, and packet output.
8
- * Supports hardware acceleration and zero-copy transcoding.
9
- *
10
- * @module api/encoder
11
- */
12
- import { AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, Codec, CodecContext, FFmpegError, Packet, Rational, } from '../lib/index.js';
13
- import { Stream } from '../lib/stream.js';
1
+ import { AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO } from '../constants/constants.js';
2
+ import { AVERROR_EAGAIN, avGetPixFmtName, avGetSampleFmtName, Codec, CodecContext, FFmpegError, Packet, Rational } from '../lib/index.js';
14
3
  import { parseBitrate } from './utils.js';
15
4
  /**
16
- * High-level encoder for media streams.
17
- *
18
- * Handles codec initialization, frame encoding, and packet output.
19
- * Supports various codecs with flexible configuration options.
5
+ * High-level encoder for audio and video streams.
20
6
  *
21
- * Manages codec context lifecycle and provides automatic cleanup.
22
- * Supports hardware acceleration with shared frames context for zero-copy.
7
+ * Provides a simplified interface for encoding media frames to packets.
8
+ * Handles codec initialization, hardware acceleration setup, and packet management.
9
+ * Supports both synchronous frame-by-frame encoding and async iteration over packets.
10
+ * Essential component in media processing pipelines for converting raw frames to compressed data.
23
11
  *
24
12
  * @example
25
13
  * ```typescript
14
+ * import { Encoder } from 'node-av/api';
15
+ * import { AV_CODEC_ID_H264 } from 'node-av/constants';
16
+ *
26
17
  * // Create H.264 encoder
27
18
  * const encoder = await Encoder.create('libx264', {
19
+ * type: 'video',
28
20
  * width: 1920,
29
21
  * height: 1080,
30
- * pixelFormat: 'yuv420p',
22
+ * pixelFormat: AV_PIX_FMT_YUV420P,
23
+ * timeBase: { num: 1, den: 30 },
24
+ * frameRate: { num: 30, den: 1 }
25
+ * }, {
31
26
  * bitrate: '5M',
32
- * gopSize: 60,
33
- * options: {
34
- * preset: 'fast',
35
- * crf: 23
36
- * }
27
+ * gopSize: 60
37
28
  * });
38
29
  *
39
30
  * // Encode frames
40
31
  * const packet = await encoder.encode(frame);
41
32
  * if (packet) {
42
- * // Write packet to output
43
- * }
44
- *
45
- * // Flush encoder
46
- * let packet;
47
- * while ((packet = await encoder.flush()) !== null) {
48
- * // Process final packets
33
+ * await output.writePacket(packet);
34
+ * packet.free();
49
35
  * }
50
- * encoder.close();
51
36
  * ```
52
37
  *
53
38
  * @example
54
39
  * ```typescript
55
- * // With hardware acceleration
56
- * const hw = await HardwareContext.auto();
57
- * const encoder = await Encoder.create('h264_videotoolbox', {
58
- * width: 1920,
59
- * height: 1080,
60
- * pixelFormat: 'nv12',
61
- * bitrate: '5M',
62
- * hardware: hw
40
+ * // Hardware-accelerated encoding
41
+ * import { HardwareContext } from 'node-av/api';
42
+ * import { AV_HWDEVICE_TYPE_CUDA } from 'node-av/constants';
43
+ *
44
+ * const hw = HardwareContext.create(AV_HWDEVICE_TYPE_CUDA);
45
+ * const encoder = await Encoder.create('h264_nvenc', streamInfo, {
46
+ * hardware: hw,
47
+ * bitrate: '10M'
63
48
  * });
64
- * // ... use encoder
65
- * encoder.close(); // Also disposes hardware
66
- * hw?.dispose(); // Safe to call again (no-op)
49
+ *
50
+ * // Frames with hw_frames_ctx will be encoded on GPU
51
+ * for await (const packet of encoder.packets(frames)) {
52
+ * await output.writePacket(packet);
53
+ * packet.free();
54
+ * }
67
55
  * ```
56
+ *
57
+ * @see {@link Decoder} For decoding packets to frames
58
+ * @see {@link MediaOutput} For writing encoded packets
59
+ * @see {@link HardwareContext} For GPU acceleration
68
60
  */
69
61
  export class Encoder {
70
62
  codecContext;
71
63
  packet;
72
- codecName;
64
+ codec;
73
65
  isOpen = true;
74
- supportedFormats = [];
75
- preferredFormat;
76
- hardware; // Store reference to check for late framesContext
77
- isHardwareEncoder = false; // Track if this is a hardware encoder
66
+ hardware;
78
67
  /**
79
- * Private constructor - use Encoder.create() instead.
80
- *
81
- * Initializes the encoder with a codec context and allocates a packet buffer.
82
- *
83
- * @param codecContext - Initialized codec context
84
- * @param codecName - Name of the codec
85
- * @param hardware - Optional hardware context for late framesContext binding
68
+ * @param codecContext - Configured codec context
69
+ * @param codec - Encoder codec
70
+ * @param hardware - Optional hardware context
71
+ * @internal
86
72
  */
87
- constructor(codecContext, codecName, hardware) {
73
+ constructor(codecContext, codec, hardware) {
88
74
  this.codecContext = codecContext;
89
- this.codecName = codecName;
75
+ this.codec = codec;
90
76
  this.hardware = hardware;
91
77
  this.packet = new Packet();
92
78
  this.packet.alloc();
@@ -94,122 +80,128 @@ export class Encoder {
94
80
  /**
95
81
  * Create an encoder with specified codec and options.
96
82
  *
97
- * Factory method that handles codec discovery, context setup,
98
- * and initialization.
83
+ * Initializes an encoder with the appropriate codec and configuration.
84
+ * Automatically configures parameters based on input stream info.
85
+ * Handles hardware acceleration setup if provided.
99
86
  *
100
- * Uses avcodec_find_encoder_by_name() to locate the codec,
101
- * configures the context with provided options, and opens it.
102
- * Handles hardware setup including shared frames context for zero-copy.
87
+ * Direct mapping to avcodec_find_encoder_by_name() or avcodec_find_encoder().
103
88
  *
104
- * @param codecName - Name of codec (e.g., 'libx264', 'aac', 'libopus')
105
- * @param input - Stream or StreamInfo to copy parameters from
89
+ * @param encoderCodec - Codec name, ID, or instance to use for encoding
90
+ * @param input - Stream information to configure encoder
106
91
  * @param options - Encoder configuration options
92
+ * @returns Configured encoder instance
107
93
  *
108
- * @returns Promise resolving to configured Encoder
109
- *
110
- * @throws {Error} If codec not found or configuration fails
94
+ * @throws {Error} If encoder not found or unsupported format
95
+ * @throws {FFmpegError} If codec initialization fails
111
96
  *
112
97
  * @example
113
98
  * ```typescript
114
- * // Video encoder from stream
115
- * const videoStream = media.video();
116
- * const videoEncoder = await Encoder.create('libx264', videoStream, {
99
+ * // From decoder stream info
100
+ * const streamInfo = decoder.getOutputStreamInfo();
101
+ * const encoder = await Encoder.create('libx264', streamInfo, {
117
102
  * bitrate: '5M',
118
- * gopSize: 60
103
+ * gopSize: 60,
104
+ * options: {
105
+ * preset: 'fast',
106
+ * crf: '23'
107
+ * }
119
108
  * });
109
+ * ```
120
110
  *
121
- * // Audio encoder from stream
122
- * const audioStream = media.audio();
123
- * const audioEncoder = await Encoder.create('aac', audioStream, {
111
+ * @example
112
+ * ```typescript
113
+ * // With custom stream info
114
+ * const encoder = await Encoder.create('aac', {
115
+ * type: 'audio',
116
+ * sampleRate: 48000,
117
+ * sampleFormat: AV_SAMPLE_FMT_FLTP,
118
+ * channelLayout: AV_CH_LAYOUT_STEREO,
119
+ * timeBase: { num: 1, den: 48000 }
120
+ * }, {
124
121
  * bitrate: '192k'
125
122
  * });
123
+ * ```
126
124
  *
125
+ * @example
126
+ * ```typescript
127
+ * // Hardware encoder
128
+ * const hw = HardwareContext.auto();
129
+ * const encoder = await Encoder.create('hevc_videotoolbox', streamInfo, {
130
+ * hardware: hw,
131
+ * bitrate: '8M'
132
+ * });
127
133
  * ```
134
+ *
135
+ * @see {@link Decoder.getOutputStreamInfo} For stream info source
136
+ * @see {@link EncoderOptions} For configuration options
128
137
  */
129
- static async create(codecName, input, options = {}) {
130
- const actualCodecName = codecName;
131
- // Find encoder by name
132
- const codec = Codec.findEncoderByName(actualCodecName);
138
+ static async create(encoderCodec, input, options = {}) {
139
+ let codec = null;
140
+ let codecName = '';
141
+ if (encoderCodec instanceof Codec) {
142
+ codec = encoderCodec;
143
+ codecName = codec.name ?? 'Unknown';
144
+ }
145
+ else if (typeof encoderCodec === 'string') {
146
+ codec = Codec.findEncoderByName(encoderCodec);
147
+ codecName = codec?.name ?? encoderCodec;
148
+ }
149
+ else {
150
+ codec = Codec.findEncoder(encoderCodec);
151
+ codecName = codec?.name ?? encoderCodec.toString();
152
+ }
133
153
  if (!codec) {
134
- throw new Error(`Encoder ${actualCodecName} not found`);
154
+ throw new Error(`Encoder ${codecName} not found`);
135
155
  }
136
156
  // Allocate codec context
137
157
  const codecContext = new CodecContext();
138
158
  codecContext.allocContext3(codec);
139
- // Apply parameters based on input type
140
- if (input instanceof Stream) {
141
- // It's a Stream - copy ONLY the essential parameters
142
- // DO NOT use parametersToContext as it copies everything including decoder-specific settings
143
- if (codec.type === AVMEDIA_TYPE_VIDEO) {
144
- // Set required video parameters from input stream
145
- codecContext.width = input.codecpar.width;
146
- codecContext.height = input.codecpar.height;
147
- codecContext.pixelFormat = input.codecpar.format;
148
- // Set timing information
149
- codecContext.pktTimebase = input.timeBase;
150
- codecContext.timeBase = input.timeBase;
151
- // Set framerate if available
152
- if (input.avgFrameRate && input.avgFrameRate.num > 0 && input.avgFrameRate.den > 0) {
153
- codecContext.framerate = new Rational(input.avgFrameRate.num, input.avgFrameRate.den);
154
- }
155
- else if (input.rFrameRate && input.rFrameRate.num > 0 && input.rFrameRate.den > 0) {
156
- codecContext.framerate = new Rational(input.rFrameRate.num, input.rFrameRate.den);
157
- }
158
- // Copy sample aspect ratio if present
159
- if (input.sampleAspectRatio && input.sampleAspectRatio.num > 0) {
160
- codecContext.sampleAspectRatio = new Rational(input.sampleAspectRatio.num, input.sampleAspectRatio.den);
161
- }
159
+ // It's StreamInfo - apply manually
160
+ if (input.type === 'video' && codec.type === AVMEDIA_TYPE_VIDEO) {
161
+ const videoInfo = input;
162
+ const codecPixelformats = codec.pixelFormats;
163
+ if (codecPixelformats && !codecPixelformats.includes(videoInfo.pixelFormat)) {
164
+ codecContext.freeContext();
165
+ const pixelFormatName = avGetPixFmtName(videoInfo.pixelFormat) ?? 'unknown';
166
+ const codecPixFmtNames = codecPixelformats.map(avGetPixFmtName).filter(Boolean).join(', ');
167
+ throw new Error(`Unsupported pixel format for '${codecName}' encoder: ${pixelFormatName}! Supported formats: ${codecPixFmtNames}`);
162
168
  }
163
- else if (codec.type === AVMEDIA_TYPE_AUDIO) {
164
- // Set required audio parameters from input stream
165
- codecContext.sampleRate = input.codecpar.sampleRate;
166
- codecContext.sampleFormat = input.codecpar.format;
167
- // Copy channel layout from input
168
- // NOTE: This means the encoder will use the same channel configuration as the input
169
- // If you need different channels, use an audio filter or pass StreamInfo instead
170
- codecContext.channelLayout = input.codecpar.channelLayout;
171
- // Set timing information
172
- codecContext.pktTimebase = input.timeBase;
173
- codecContext.timeBase = input.timeBase;
169
+ codecContext.width = videoInfo.width;
170
+ codecContext.height = videoInfo.height;
171
+ codecContext.pixelFormat = videoInfo.pixelFormat;
172
+ // Set pkt_timebase and timeBase to input timebase
173
+ codecContext.pktTimebase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
174
+ codecContext.timeBase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
175
+ if (videoInfo.frameRate) {
176
+ codecContext.framerate = new Rational(videoInfo.frameRate.num, videoInfo.frameRate.den);
174
177
  }
175
- else {
176
- codecContext.freeContext();
177
- throw new Error('Unsupported codec type for encoder');
178
+ if (videoInfo.sampleAspectRatio) {
179
+ codecContext.sampleAspectRatio = new Rational(videoInfo.sampleAspectRatio.num, videoInfo.sampleAspectRatio.den);
178
180
  }
179
181
  }
180
- else {
181
- // It's StreamInfo - apply manually
182
- if (input.type === 'video' && codec.type === AVMEDIA_TYPE_VIDEO) {
183
- const videoInfo = input;
184
- codecContext.width = videoInfo.width;
185
- codecContext.height = videoInfo.height;
186
- codecContext.pixelFormat = videoInfo.pixelFormat;
187
- // Set pkt_timebase and timeBase to input timebase
188
- codecContext.pktTimebase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
189
- codecContext.timeBase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
190
- if (videoInfo.frameRate) {
191
- codecContext.framerate = new Rational(videoInfo.frameRate.num, videoInfo.frameRate.den);
192
- }
193
- if (videoInfo.sampleAspectRatio) {
194
- codecContext.sampleAspectRatio = new Rational(videoInfo.sampleAspectRatio.num, videoInfo.sampleAspectRatio.den);
195
- }
196
- }
197
- else if (input.type === 'audio' && codec.type === AVMEDIA_TYPE_AUDIO) {
198
- const audioInfo = input;
199
- codecContext.sampleRate = audioInfo.sampleRate;
200
- codecContext.sampleFormat = audioInfo.sampleFormat;
201
- codecContext.channelLayout = audioInfo.channelLayout;
202
- // Set both pkt_timebase and timeBase for audio
203
- codecContext.pktTimebase = new Rational(audioInfo.timeBase.num, audioInfo.timeBase.den);
204
- codecContext.timeBase = new Rational(audioInfo.timeBase.num, audioInfo.timeBase.den);
205
- if (audioInfo.frameSize) {
206
- codecContext.frameSize = audioInfo.frameSize;
207
- }
182
+ else if (input.type === 'audio' && codec.type === AVMEDIA_TYPE_AUDIO) {
183
+ const audioInfo = input;
184
+ const codecSampleFormats = codec.sampleFormats;
185
+ if (codecSampleFormats && !codecSampleFormats.includes(audioInfo.sampleFormat)) {
186
+ codecContext.freeContext();
187
+ const sampleFormatName = avGetSampleFmtName(audioInfo.sampleFormat) ?? 'unknown';
188
+ const supportedFormats = codecSampleFormats.map(avGetSampleFmtName).filter(Boolean).join(', ');
189
+ throw new Error(`Unsupported sample format for '${codecName}' encoder: ${sampleFormatName}! Supported formats: ${supportedFormats}`);
208
190
  }
209
- else {
210
- throw new Error(`Codec type mismatch: ${input.type} info but ${codec.type === AVMEDIA_TYPE_VIDEO ? 'video' : 'audio'} codec`);
191
+ codecContext.sampleRate = audioInfo.sampleRate;
192
+ codecContext.sampleFormat = audioInfo.sampleFormat;
193
+ codecContext.channelLayout = audioInfo.channelLayout;
194
+ // Set both pkt_timebase and timeBase for audio
195
+ codecContext.pktTimebase = new Rational(audioInfo.timeBase.num, audioInfo.timeBase.den);
196
+ codecContext.timeBase = new Rational(audioInfo.timeBase.num, audioInfo.timeBase.den);
197
+ if (audioInfo.frameSize) {
198
+ codecContext.frameSize = audioInfo.frameSize;
211
199
  }
212
200
  }
201
+ else {
202
+ codecContext.freeContext();
203
+ throw new Error(`Unsupported codec type for encoder! Input type: ${input.type}, Codec type: ${codec.type}`);
204
+ }
213
205
  // Apply encoder-specific options
214
206
  if (options.gopSize !== undefined) {
215
207
  codecContext.gopSize = options.gopSize;
@@ -235,106 +227,102 @@ export class Encoder {
235
227
  codecContext.setOption(key, value.toString());
236
228
  }
237
229
  }
238
- // Check if this encoder supports hardware acceleration
239
- let supportsHardware = false;
240
- let isHardwareEncoder = false;
241
- // Check encoder's hardware configurations
242
- for (let i = 0;; i++) {
243
- const config = codec.getHwConfig(i);
244
- if (!config)
245
- break;
246
- // Check if encoder supports HW_FRAMES_CTX method
247
- if ((config.methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) !== 0) {
248
- supportsHardware = true;
249
- // If hardware context provided, check if it matches this encoder
250
- if (options.hardware && config.deviceType === options.hardware.deviceType) {
251
- isHardwareEncoder = true;
252
- break;
253
- }
254
- }
255
- }
256
- // Validation: Hardware encoder MUST have HardwareContext
257
- if (supportsHardware && !options.hardware) {
258
- throw new Error(`Hardware encoder '${actualCodecName}' requires a hardware context. ` + 'Please provide one via options.hardware');
259
- }
260
- // Apply hardware acceleration if provided and encoder supports it
261
- if (options.hardware && isHardwareEncoder) {
262
- // Check if frames context already available (shared from decoder)
263
- if (options.hardware.framesContext) {
264
- codecContext.hwFramesCtx = options.hardware.framesContext;
265
- codecContext.pixelFormat = options.hardware.getHardwarePixelFormat();
266
- }
267
- // Else: Will set hwFramesCtx later in encode() when first frame arrives
268
- // DO NOT create frames context here - wait for first frame!
230
+ const isHWEncoder = codec.isHardwareAcceleratedEncoder();
231
+ if (isHWEncoder && !options.hardware) {
232
+ codecContext.freeContext();
233
+ throw new Error(`Hardware encoder '${codecName}' requires a hardware context`);
269
234
  }
270
- // Note: Software encoder silently ignores hardware context
271
235
  // Open codec
272
236
  const openRet = await codecContext.open2(codec, null);
273
237
  if (openRet < 0) {
274
238
  codecContext.freeContext();
275
239
  FFmpegError.throwIfError(openRet, 'Failed to open encoder');
276
240
  }
277
- const encoder = new Encoder(codecContext, codecName, isHardwareEncoder ? options.hardware : undefined);
278
- encoder.isHardwareEncoder = isHardwareEncoder;
279
- // Get supported formats from codec (for validation and helpers)
280
- if (codec.pixelFormats) {
281
- encoder.supportedFormats = codec.pixelFormats;
282
- encoder.preferredFormat = encoder.supportedFormats[0];
283
- }
241
+ const encoder = new Encoder(codecContext, codec, isHWEncoder ? options.hardware : undefined);
284
242
  return encoder;
285
243
  }
286
244
  /**
287
245
  * Check if encoder is open.
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * if (encoder.isEncoderOpen) {
250
+ * const packet = await encoder.encode(frame);
251
+ * }
252
+ * ```
288
253
  */
289
254
  get isEncoderOpen() {
290
255
  return this.isOpen;
291
256
  }
292
257
  /**
293
- * Encode a frame and return a packet if available.
258
+ * Check if encoder uses hardware acceleration.
259
+ *
260
+ * @returns true if hardware-accelerated
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * if (encoder.isHardware()) {
265
+ * console.log('Using GPU acceleration');
266
+ * }
267
+ * ```
294
268
  *
295
- * Sends frame to encoder and attempts to receive a packet.
296
- * May return null if encoder needs more data.
269
+ * @see {@link HardwareContext} For hardware setup
270
+ */
271
+ isHardware() {
272
+ return !!this.hardware;
273
+ }
274
+ /**
275
+ * Encode a frame to a packet.
297
276
  *
298
- * Uses avcodec_send_frame() and avcodec_receive_packet() internally.
299
- * The encoder may buffer frames before producing packets.
277
+ * Sends a frame to the encoder and attempts to receive an encoded packet.
278
+ * Handles internal buffering - may return null if more frames needed.
279
+ * Automatically manages encoder state and hardware context binding.
300
280
  *
301
- * @param frame - Frame to encode (or null to flush)
281
+ * Direct mapping to avcodec_send_frame() and avcodec_receive_packet().
302
282
  *
303
- * @returns Promise resolving to Packet or null
283
+ * @param frame - Raw frame to encode (or null to flush)
284
+ * @returns Encoded packet or null if more data needed
304
285
  *
305
- * @throws {Error} If encoder is closed or encode fails
286
+ * @throws {Error} If encoder is closed
287
+ * @throws {FFmpegError} If encoding fails
306
288
  *
307
289
  * @example
308
290
  * ```typescript
309
291
  * const packet = await encoder.encode(frame);
310
292
  * if (packet) {
311
- * // Write packet to output
293
+ * console.log(`Encoded packet with PTS: ${packet.pts}`);
312
294
  * await output.writePacket(packet);
295
+ * packet.free();
296
+ * }
297
+ * ```
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * // Encode loop
302
+ * for await (const frame of decoder.frames(input.packets())) {
303
+ * const packet = await encoder.encode(frame);
304
+ * if (packet) {
305
+ * await output.writePacket(packet);
306
+ * packet.free();
307
+ * }
308
+ * frame.free();
313
309
  * }
314
310
  * ```
311
+ *
312
+ * @see {@link packets} For automatic frame iteration
313
+ * @see {@link flush} For end-of-stream handling
315
314
  */
316
315
  async encode(frame) {
317
316
  if (!this.isOpen) {
318
317
  throw new Error('Encoder is closed');
319
318
  }
320
- // Late binding of hw_frames_ctx ONLY for hardware encoders
321
- if (this.hardware && this.isHardwareEncoder && !this.codecContext.hwFramesCtx) {
322
- if (this.hardware.framesContext) {
323
- // Use shared frames context from decoder
324
- this.codecContext.hwFramesCtx = this.hardware.framesContext;
325
- }
326
- else if (frame?.width && frame.height) {
327
- // First frame - create frames context for upload
328
- this.hardware.ensureFramesContext(frame.width, frame.height);
329
- this.codecContext.hwFramesCtx = this.hardware.framesContext ?? null;
330
- }
331
- if (this.codecContext.hwFramesCtx) {
332
- this.codecContext.pixelFormat = this.hardware.getHardwarePixelFormat();
333
- }
319
+ // Late binding of hw_frames_ctx for hardware encoders
320
+ // Hardware encoders get hw_frames_ctx from the frames they receive
321
+ if (this.hardware && frame?.hwFramesCtx && !this.codecContext.hwFramesCtx) {
322
+ // Use the hw_frames_ctx from the frame
323
+ this.codecContext.hwFramesCtx = frame.hwFramesCtx;
324
+ this.codecContext.pixelFormat = this.hardware.devicePixelFormat;
334
325
  }
335
- // Software encoder completely ignores hardware context
336
- // NO MANUAL RESCALING! FFmpeg's avcodec_send_frame() does this internally
337
- // The encoder needs pkt_timebase to be set for proper rescaling
338
326
  // Send frame to encoder
339
327
  const sendRet = await this.codecContext.sendFrame(frame);
340
328
  if (sendRet < 0 && sendRet !== AVERROR_EOF) {
@@ -348,33 +336,65 @@ export class Encoder {
348
336
  }
349
337
  }
350
338
  // Try to receive packet
351
- return this.receivePacket();
339
+ return await this.receivePacket();
352
340
  }
353
341
  /**
354
- * Async iterator that encodes frames and yields packets.
355
- *
356
- * Encodes all provided frames and yields resulting packets.
357
- * Automatically handles encoder flushing at the end.
358
- * Input frames are automatically freed after encoding.
342
+ * Encode frame stream to packet stream.
359
343
  *
360
- * Processes frames in sequence, encoding each and yielding packets.
361
- * After all frames are processed, flushes the encoder for remaining packets.
344
+ * High-level async generator for complete encoding pipeline.
345
+ * Automatically manages frame memory, encoder state,
346
+ * and flushes buffered packets at end.
347
+ * Primary interface for stream-based encoding.
362
348
  *
363
- * IMPORTANT: The yielded packets MUST be freed by the caller!
364
- * Input frames are automatically freed after processing.
365
- *
366
- * @param frames - Async iterable of frames to encode (will be freed automatically)
349
+ * @param frames - Async iterable of frames (freed automatically)
350
+ * @yields Encoded packets (caller must free)
351
+ * @throws {Error} If encoder is closed
352
+ * @throws {FFmpegError} If encoding fails
367
353
  *
368
- * @yields Encoded packets (ownership transferred to caller)
354
+ * @example
355
+ * ```typescript
356
+ * // Basic encoding pipeline
357
+ * for await (const packet of encoder.packets(decoder.frames(input.packets()))) {
358
+ * await output.writePacket(packet);
359
+ * packet.free(); // Must free output packets
360
+ * }
361
+ * ```
369
362
  *
370
363
  * @example
371
364
  * ```typescript
372
- * // Transcode video
373
- * for await (const packet of encoder.packets(decoder.frames(media.packets()))) {
365
+ * // With frame filtering
366
+ * async function* filteredFrames() {
367
+ * for await (const frame of decoder.frames(input.packets())) {
368
+ * await filter.filterFrame(frame);
369
+ * const filtered = await filter.getFrame();
370
+ * if (filtered) {
371
+ * yield filtered;
372
+ * }
373
+ * }
374
+ * }
375
+ *
376
+ * for await (const packet of encoder.packets(filteredFrames())) {
374
377
  * await output.writePacket(packet);
375
- * packet.free(); // Must free output packet
378
+ * packet.free();
376
379
  * }
377
380
  * ```
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * // Pipeline integration
385
+ * import { pipeline } from 'node-av/api';
386
+ *
387
+ * const control = pipeline(
388
+ * input,
389
+ * decoder,
390
+ * encoder,
391
+ * output
392
+ * );
393
+ * await control.completion;
394
+ * ```
395
+ *
396
+ * @see {@link encode} For single frame encoding
397
+ * @see {@link Decoder.frames} For frame source
378
398
  */
379
399
  async *packets(frames) {
380
400
  if (!this.isOpen) {
@@ -400,27 +420,31 @@ export class Encoder {
400
420
  }
401
421
  }
402
422
  /**
403
- * Flush encoder and get remaining packets.
423
+ * Flush encoder and get buffered packet.
404
424
  *
405
- * Sends null frame to trigger flush mode.
406
- * Call repeatedly until it returns null.
425
+ * Signals end-of-stream and retrieves remaining packets.
426
+ * Call repeatedly until null to get all buffered packets.
427
+ * Essential for ensuring all frames are encoded.
407
428
  *
408
- * Uses avcodec_send_frame(NULL) to signal end of stream.
409
- * Retrieves buffered packets from the encoder.
429
+ * Direct mapping to avcodec_send_frame(NULL).
410
430
  *
411
- * @returns Promise resolving to Packet or null
431
+ * @returns Buffered packet or null if none remaining
412
432
  *
413
433
  * @throws {Error} If encoder is closed
414
434
  *
415
435
  * @example
416
436
  * ```typescript
417
- * // Flush all remaining packets
437
+ * // Flush remaining packets
418
438
  * let packet;
419
439
  * while ((packet = await encoder.flush()) !== null) {
420
- * // Write final packets
440
+ * console.log('Got buffered packet');
421
441
  * await output.writePacket(packet);
442
+ * packet.free();
422
443
  * }
423
444
  * ```
445
+ *
446
+ * @see {@link flushPackets} For async iteration
447
+ * @see {@link packets} For complete encoding pipeline
424
448
  */
425
449
  async flush() {
426
450
  if (!this.isOpen) {
@@ -429,26 +453,30 @@ export class Encoder {
429
453
  // Send flush frame (null)
430
454
  await this.codecContext.sendFrame(null);
431
455
  // Receive packet
432
- return this.receivePacket();
456
+ return await this.receivePacket();
433
457
  }
434
458
  /**
435
- * Flush encoder and yield all remaining packets as a generator.
436
- *
437
- * More convenient than calling flush() in a loop.
438
- * Automatically sends flush signal and yields all buffered packets.
459
+ * Flush all buffered packets as async generator.
439
460
  *
440
- * @returns Async generator of remaining packets
461
+ * Convenient async iteration over remaining packets.
462
+ * Automatically handles repeated flush calls.
463
+ * Useful for end-of-stream processing.
441
464
  *
465
+ * @yields Buffered packets
442
466
  * @throws {Error} If encoder is closed
443
467
  *
444
468
  * @example
445
469
  * ```typescript
446
- * // Process all remaining packets with generator
470
+ * // Flush at end of encoding
447
471
  * for await (const packet of encoder.flushPackets()) {
448
- * await output.writePacket(packet, streamIdx);
449
- * using _ = packet; // Auto cleanup
472
+ * console.log('Processing buffered packet');
473
+ * await output.writePacket(packet);
474
+ * packet.free();
450
475
  * }
451
476
  * ```
477
+ *
478
+ * @see {@link flush} For single packet flush
479
+ * @see {@link packets} For complete pipeline
452
480
  */
453
481
  async *flushPackets() {
454
482
  if (!this.isOpen) {
@@ -462,84 +490,74 @@ export class Encoder {
462
490
  /**
463
491
  * Close encoder and free resources.
464
492
  *
465
- * After closing, the encoder cannot be used again.
493
+ * Releases codec context and internal packet buffer.
494
+ * Safe to call multiple times.
495
+ * Does NOT dispose hardware context - caller is responsible.
496
+ * Automatically called by Symbol.dispose.
466
497
  *
467
- * Frees the packet buffer and codec context.
468
- * Note: Does NOT dispose the HardwareContext - caller is responsible for that.
498
+ * @example
499
+ * ```typescript
500
+ * const encoder = await Encoder.create('libx264', streamInfo);
501
+ * try {
502
+ * // Use encoder
503
+ * } finally {
504
+ * encoder.close();
505
+ * }
506
+ * ```
507
+ *
508
+ * @see {@link Symbol.dispose} For automatic cleanup
469
509
  */
470
510
  close() {
471
511
  if (!this.isOpen)
472
512
  return;
473
513
  this.packet.free();
474
514
  this.codecContext.freeContext();
475
- // NOTE: We do NOT dispose the hardware context here anymore
476
- // The caller who created the HardwareContext is responsible for disposing it
477
- // This allows reusing the same HardwareContext for multiple encoders
478
515
  this.isOpen = false;
479
516
  }
480
517
  /**
481
- * Get the codec name.
482
- */
483
- getCodecName() {
484
- return this.codecName;
485
- }
486
- /**
487
- * Get codec context for advanced configuration.
488
- *
489
- * Use with caution - direct manipulation may cause issues.
490
- *
491
- * Provides access to the underlying AVCodecContext for advanced operations.
492
- *
493
- * @returns CodecContext or null if closed
494
- *
495
- * @internal
496
- */
497
- getCodecContext() {
498
- return this.isOpen ? this.codecContext : null;
499
- }
500
- /**
501
- * Get the preferred pixel format for this encoder.
518
+ * Get encoder codec.
502
519
  *
503
- * Returns the first supported format, which is usually the most efficient.
520
+ * Returns the codec used by this encoder.
521
+ * Useful for checking codec capabilities and properties.
504
522
  *
505
- * @returns Preferred pixel format or null if not available
523
+ * @returns Codec instance
506
524
  *
507
525
  * @example
508
526
  * ```typescript
509
- * const format = encoder.getPreferredPixelFormat();
510
- * if (format) {
511
- * console.log(`Encoder prefers format: ${format}`);
512
- * }
527
+ * const codec = encoder.getCodec();
528
+ * console.log(`Using codec: ${codec.name}`);
529
+ * console.log(`Capabilities: ${codec.capabilities}`);
513
530
  * ```
531
+ *
532
+ * @see {@link Codec} For codec properties
514
533
  */
515
- getPreferredPixelFormat() {
516
- return this.preferredFormat ?? null;
534
+ getCodec() {
535
+ return this.codec;
517
536
  }
518
537
  /**
519
- * Get all supported pixel formats for this encoder.
538
+ * Get underlying codec context.
520
539
  *
521
- * Returns a list of pixel formats that this encoder can accept.
540
+ * Returns the internal codec context for advanced operations.
541
+ * Returns null if encoder is closed.
522
542
  *
523
- * @returns Array of supported pixel formats
543
+ * @returns Codec context or null
524
544
  *
525
- * @example
526
- * ```typescript
527
- * const formats = encoder.getSupportedPixelFormats();
528
- * console.log(`Encoder supports: ${formats.join(', ')}`);
529
- * ```
545
+ * @internal
530
546
  */
531
- getSupportedPixelFormats() {
532
- return this.supportedFormats;
547
+ getCodecContext() {
548
+ return this.isOpen ? this.codecContext : null;
533
549
  }
534
550
  /**
535
- * Receive a packet from the encoder.
551
+ * Receive packet from encoder.
536
552
  *
537
- * Internal method to receive encoded packets.
553
+ * Internal method to get encoded packets from codec.
554
+ * Handles packet cloning and error checking.
538
555
  *
539
- * Uses avcodec_receive_packet() to get encoded packets from the codec.
540
- * Clones the packet for the user to prevent internal buffer corruption.
556
+ * Direct mapping to avcodec_receive_packet().
541
557
  *
542
- * @returns Packet or null if no packet available
558
+ * @returns Cloned packet or null
559
+ *
560
+ * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
543
561
  */
544
562
  async receivePacket() {
545
563
  // Clear previous packet data
@@ -560,10 +578,20 @@ export class Encoder {
560
578
  }
561
579
  }
562
580
  /**
563
- * Symbol.dispose for automatic cleanup.
581
+ * Dispose of encoder.
582
+ *
583
+ * Implements Disposable interface for automatic cleanup.
584
+ * Equivalent to calling close().
585
+ *
586
+ * @example
587
+ * ```typescript
588
+ * {
589
+ * using encoder = await Encoder.create('libx264', streamInfo);
590
+ * // Encode frames...
591
+ * } // Automatically closed
592
+ * ```
564
593
  *
565
- * Implements the Disposable interface for automatic resource management.
566
- * Calls close() to free all resources.
594
+ * @see {@link close} For manual cleanup
567
595
  */
568
596
  [Symbol.dispose]() {
569
597
  this.close();