@pproenca/node-webcodecs 0.1.0 → 0.1.1-alpha.5
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.
- package/README.md +78 -206
- package/binding.gyp +123 -0
- package/dist/audio-decoder.js +1 -2
- package/dist/audio-encoder.d.ts +4 -0
- package/dist/audio-encoder.js +28 -2
- package/dist/binding.d.ts +0 -2
- package/dist/binding.js +43 -124
- package/dist/control-message-queue.js +0 -1
- package/dist/demuxer.d.ts +7 -0
- package/dist/demuxer.js +9 -0
- package/dist/encoded-chunks.d.ts +16 -0
- package/dist/encoded-chunks.js +82 -2
- package/dist/image-decoder.js +4 -0
- package/dist/index.d.ts +17 -3
- package/dist/index.js +9 -4
- package/dist/is.d.ts +18 -0
- package/dist/is.js +14 -0
- package/dist/native-types.d.ts +20 -0
- package/dist/platform.d.ts +1 -10
- package/dist/platform.js +1 -39
- package/dist/resource-manager.d.ts +1 -2
- package/dist/resource-manager.js +3 -17
- package/dist/types.d.ts +46 -0
- package/dist/video-decoder.d.ts +21 -0
- package/dist/video-decoder.js +74 -2
- package/dist/video-encoder.d.ts +22 -0
- package/dist/video-encoder.js +83 -8
- package/dist/video-frame.d.ts +6 -3
- package/dist/video-frame.js +36 -4
- package/lib/audio-decoder.ts +1 -2
- package/lib/audio-encoder.ts +31 -2
- package/lib/binding.ts +45 -104
- package/lib/control-message-queue.ts +0 -1
- package/lib/demuxer.ts +10 -0
- package/lib/encoded-chunks.ts +90 -2
- package/lib/image-decoder.ts +5 -0
- package/lib/index.ts +9 -3
- package/lib/is.ts +32 -0
- package/lib/native-types.ts +22 -0
- package/lib/platform.ts +1 -41
- package/lib/resource-manager.ts +3 -19
- package/lib/types.ts +52 -1
- package/lib/video-decoder.ts +84 -2
- package/lib/video-encoder.ts +90 -8
- package/lib/video-frame.ts +52 -7
- package/package.json +49 -32
- package/src/addon.cc +57 -0
- package/src/async_decode_worker.cc +243 -36
- package/src/async_decode_worker.h +55 -4
- package/src/async_encode_worker.cc +155 -44
- package/src/async_encode_worker.h +38 -12
- package/src/audio_data.cc +38 -15
- package/src/audio_data.h +1 -0
- package/src/audio_decoder.cc +24 -3
- package/src/audio_encoder.cc +55 -4
- package/src/common.cc +125 -17
- package/src/common.h +34 -4
- package/src/demuxer.cc +16 -2
- package/src/encoded_audio_chunk.cc +10 -0
- package/src/encoded_audio_chunk.h +2 -0
- package/src/encoded_video_chunk.h +1 -0
- package/src/error_builder.cc +0 -4
- package/src/image_decoder.cc +127 -90
- package/src/image_decoder.h +11 -4
- package/src/muxer.cc +1 -0
- package/src/test_video_generator.cc +3 -2
- package/src/video_decoder.cc +169 -19
- package/src/video_decoder.h +9 -11
- package/src/video_encoder.cc +428 -35
- package/src/video_encoder.h +16 -0
- package/src/video_filter.cc +22 -11
- package/src/video_frame.cc +160 -5
- package/src/warnings.cc +0 -4
- package/dist/audio-data.js.map +0 -1
- package/dist/audio-decoder.js.map +0 -1
- package/dist/audio-encoder.js.map +0 -1
- package/dist/binding.js.map +0 -1
- package/dist/codec-base.js.map +0 -1
- package/dist/control-message-queue.js.map +0 -1
- package/dist/demuxer.js.map +0 -1
- package/dist/encoded-chunks.js.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/ffmpeg.d.ts +0 -21
- package/dist/ffmpeg.js +0 -112
- package/dist/image-decoder.js.map +0 -1
- package/dist/image-track-list.js.map +0 -1
- package/dist/image-track.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/is.js.map +0 -1
- package/dist/muxer.js.map +0 -1
- package/dist/native-types.js.map +0 -1
- package/dist/platform.js.map +0 -1
- package/dist/resource-manager.js.map +0 -1
- package/dist/test-video-generator.js.map +0 -1
- package/dist/transfer.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/video-decoder.js.map +0 -1
- package/dist/video-encoder.js.map +0 -1
- package/dist/video-filter.js.map +0 -1
- package/dist/video-frame.js.map +0 -1
- package/install/build.js +0 -51
- package/install/check.js +0 -192
- package/lib/ffmpeg.ts +0 -78
package/src/image_decoder.cc
CHANGED
|
@@ -19,6 +19,19 @@
|
|
|
19
19
|
// Buffer size for AVIOContext (4KB is typical for image data)
|
|
20
20
|
static const int kAVIOBufferSize = 4096;
|
|
21
21
|
|
|
22
|
+
// Premultiply alpha in-place for RGBA data.
|
|
23
|
+
// Applies premultiplication: R' = R * A / 255, G' = G * A / 255, B' = B * A / 255
|
|
24
|
+
// Uses (value * alpha + 127) / 255 for correct rounding.
|
|
25
|
+
static void PremultiplyAlpha(uint8_t* rgba_data, int width, int height) {
|
|
26
|
+
for (int i = 0; i < width * height; i++) {
|
|
27
|
+
uint8_t* pixel = rgba_data + i * 4;
|
|
28
|
+
uint8_t alpha = pixel[3];
|
|
29
|
+
pixel[0] = static_cast<uint8_t>((pixel[0] * alpha + 127) / 255);
|
|
30
|
+
pixel[1] = static_cast<uint8_t>((pixel[1] * alpha + 127) / 255);
|
|
31
|
+
pixel[2] = static_cast<uint8_t>((pixel[2] * alpha + 127) / 255);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
// Custom read callback for AVIOContext to read from memory buffer
|
|
23
36
|
struct MemoryBufferContext {
|
|
24
37
|
const uint8_t* data;
|
|
@@ -65,6 +78,9 @@ static int64_t SeekPacket(void* opaque, int64_t offset, int whence) {
|
|
|
65
78
|
return new_pos;
|
|
66
79
|
}
|
|
67
80
|
|
|
81
|
+
// Static member definition
|
|
82
|
+
Napi::FunctionReference ImageDecoder::constructor_;
|
|
83
|
+
|
|
68
84
|
Napi::Object ImageDecoder::Init(Napi::Env env, Napi::Object exports) {
|
|
69
85
|
Napi::Function func = DefineClass(
|
|
70
86
|
env, "ImageDecoder",
|
|
@@ -77,9 +93,9 @@ Napi::Object ImageDecoder::Init(Napi::Env env, Napi::Object exports) {
|
|
|
77
93
|
StaticMethod("isTypeSupported", &ImageDecoder::IsTypeSupported),
|
|
78
94
|
});
|
|
79
95
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
// Use static member pattern to avoid heap allocation leak
|
|
97
|
+
constructor_ = Napi::Persistent(func);
|
|
98
|
+
constructor_.SuppressDestruct();
|
|
83
99
|
|
|
84
100
|
exports.Set("ImageDecoder", func);
|
|
85
101
|
return exports;
|
|
@@ -88,12 +104,13 @@ Napi::Object ImageDecoder::Init(Napi::Env env, Napi::Object exports) {
|
|
|
88
104
|
ImageDecoder::ImageDecoder(const Napi::CallbackInfo& info)
|
|
89
105
|
: Napi::ObjectWrap<ImageDecoder>(info),
|
|
90
106
|
codec_(nullptr),
|
|
91
|
-
codec_context_(
|
|
92
|
-
sws_context_(
|
|
93
|
-
frame_(
|
|
94
|
-
packet_(
|
|
107
|
+
codec_context_(),
|
|
108
|
+
sws_context_(),
|
|
109
|
+
frame_(),
|
|
110
|
+
packet_(),
|
|
95
111
|
format_context_(nullptr),
|
|
96
112
|
avio_context_(nullptr),
|
|
113
|
+
mem_ctx_(nullptr),
|
|
97
114
|
video_stream_index_(-1),
|
|
98
115
|
decoded_width_(0),
|
|
99
116
|
decoded_height_(0),
|
|
@@ -101,7 +118,8 @@ ImageDecoder::ImageDecoder(const Napi::CallbackInfo& info)
|
|
|
101
118
|
frame_count_(1),
|
|
102
119
|
repetition_count_(0),
|
|
103
120
|
complete_(false),
|
|
104
|
-
closed_(false)
|
|
121
|
+
closed_(false),
|
|
122
|
+
premultiply_alpha_("default") {
|
|
105
123
|
Napi::Env env = info.Env();
|
|
106
124
|
|
|
107
125
|
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
@@ -120,6 +138,16 @@ ImageDecoder::ImageDecoder(const Napi::CallbackInfo& info)
|
|
|
120
138
|
}
|
|
121
139
|
type_ = webcodecs::AttrAsStr(init, "type");
|
|
122
140
|
|
|
141
|
+
// Get premultiplyAlpha option
|
|
142
|
+
premultiply_alpha_ = webcodecs::AttrAsStr(init, "premultiplyAlpha", "default");
|
|
143
|
+
if (premultiply_alpha_ != "none" && premultiply_alpha_ != "premultiply" &&
|
|
144
|
+
premultiply_alpha_ != "default") {
|
|
145
|
+
Napi::TypeError::New(
|
|
146
|
+
env, "premultiplyAlpha must be 'none', 'premultiply', or 'default'")
|
|
147
|
+
.ThrowAsJavaScriptException();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
123
151
|
// Get data
|
|
124
152
|
if (!init.Has("data")) {
|
|
125
153
|
Napi::TypeError::New(env, "data is required").ThrowAsJavaScriptException();
|
|
@@ -159,8 +187,8 @@ ImageDecoder::ImageDecoder(const Napi::CallbackInfo& info)
|
|
|
159
187
|
return;
|
|
160
188
|
}
|
|
161
189
|
|
|
162
|
-
// Create codec context
|
|
163
|
-
codec_context_ =
|
|
190
|
+
// Create codec context using RAII wrapper
|
|
191
|
+
codec_context_ = ffmpeg::make_codec_context(codec_);
|
|
164
192
|
if (!codec_context_) {
|
|
165
193
|
Napi::Error::New(env, "Failed to allocate codec context")
|
|
166
194
|
.ThrowAsJavaScriptException();
|
|
@@ -168,15 +196,15 @@ ImageDecoder::ImageDecoder(const Napi::CallbackInfo& info)
|
|
|
168
196
|
}
|
|
169
197
|
|
|
170
198
|
// Open codec
|
|
171
|
-
if (avcodec_open2(codec_context_, codec_, nullptr) < 0) {
|
|
199
|
+
if (avcodec_open2(codec_context_.get(), codec_, nullptr) < 0) {
|
|
172
200
|
Cleanup();
|
|
173
201
|
Napi::Error::New(env, "Failed to open codec").ThrowAsJavaScriptException();
|
|
174
202
|
return;
|
|
175
203
|
}
|
|
176
204
|
|
|
177
|
-
// Allocate frame and packet
|
|
178
|
-
frame_ =
|
|
179
|
-
packet_ =
|
|
205
|
+
// Allocate frame and packet using RAII wrappers
|
|
206
|
+
frame_ = ffmpeg::make_frame();
|
|
207
|
+
packet_ = ffmpeg::make_packet();
|
|
180
208
|
if (!frame_ || !packet_) {
|
|
181
209
|
Cleanup();
|
|
182
210
|
Napi::Error::New(env, "Failed to allocate frame/packet")
|
|
@@ -205,26 +233,20 @@ ImageDecoder::ImageDecoder(const Napi::CallbackInfo& info)
|
|
|
205
233
|
ImageDecoder::~ImageDecoder() { Cleanup(); }
|
|
206
234
|
|
|
207
235
|
void ImageDecoder::Cleanup() {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
av_frame_free(&frame_);
|
|
214
|
-
frame_ = nullptr;
|
|
215
|
-
}
|
|
216
|
-
if (packet_) {
|
|
217
|
-
av_packet_free(&packet_);
|
|
218
|
-
packet_ = nullptr;
|
|
219
|
-
}
|
|
220
|
-
if (codec_context_) {
|
|
221
|
-
avcodec_free_context(&codec_context_);
|
|
222
|
-
codec_context_ = nullptr;
|
|
223
|
-
}
|
|
236
|
+
// RAII wrappers handle deallocation automatically via reset()
|
|
237
|
+
sws_context_.reset();
|
|
238
|
+
frame_.reset();
|
|
239
|
+
packet_.reset();
|
|
240
|
+
codec_context_.reset();
|
|
224
241
|
if (format_context_) {
|
|
225
242
|
avformat_close_input(&format_context_);
|
|
226
243
|
format_context_ = nullptr;
|
|
227
244
|
}
|
|
245
|
+
// Free MemoryBufferContext BEFORE avio_context_free (it's stored in opaque)
|
|
246
|
+
if (mem_ctx_) {
|
|
247
|
+
delete mem_ctx_;
|
|
248
|
+
mem_ctx_ = nullptr;
|
|
249
|
+
}
|
|
228
250
|
if (avio_context_) {
|
|
229
251
|
// The buffer is freed by avio_context_free
|
|
230
252
|
av_freep(&avio_context_->buffer);
|
|
@@ -261,12 +283,12 @@ bool ImageDecoder::ConvertFrameToRGBA(AVFrame* src_frame,
|
|
|
261
283
|
return false;
|
|
262
284
|
}
|
|
263
285
|
|
|
264
|
-
// Create swscale context for conversion to RGBA
|
|
265
|
-
|
|
286
|
+
// Create swscale context for conversion to RGBA (RAII managed)
|
|
287
|
+
ffmpeg::SwsContextPtr local_sws(
|
|
266
288
|
sws_getContext(src_frame->width, src_frame->height,
|
|
267
289
|
static_cast<AVPixelFormat>(src_frame->format),
|
|
268
290
|
src_frame->width, src_frame->height, AV_PIX_FMT_RGBA,
|
|
269
|
-
SWS_BILINEAR, nullptr, nullptr, nullptr);
|
|
291
|
+
SWS_BILINEAR, nullptr, nullptr, nullptr));
|
|
270
292
|
|
|
271
293
|
if (!local_sws) {
|
|
272
294
|
return false;
|
|
@@ -282,10 +304,14 @@ bool ImageDecoder::ConvertFrameToRGBA(AVFrame* src_frame,
|
|
|
282
304
|
int dest_linesize[4] = {src_frame->width * 4, 0, 0, 0};
|
|
283
305
|
|
|
284
306
|
// Convert
|
|
285
|
-
sws_scale(local_sws, src_frame->data, src_frame->linesize, 0,
|
|
307
|
+
sws_scale(local_sws.get(), src_frame->data, src_frame->linesize, 0,
|
|
286
308
|
src_frame->height, dest_data, dest_linesize);
|
|
287
309
|
|
|
288
|
-
|
|
310
|
+
// Apply alpha premultiplication if requested
|
|
311
|
+
if (premultiply_alpha_ == "premultiply") {
|
|
312
|
+
PremultiplyAlpha(output->data(), src_frame->width, src_frame->height);
|
|
313
|
+
}
|
|
314
|
+
|
|
289
315
|
return true;
|
|
290
316
|
}
|
|
291
317
|
|
|
@@ -295,24 +321,26 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
295
321
|
}
|
|
296
322
|
|
|
297
323
|
// Allocate memory buffer context for custom I/O
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
324
|
+
mem_ctx_ = new MemoryBufferContext();
|
|
325
|
+
mem_ctx_->data = data_.data();
|
|
326
|
+
mem_ctx_->size = data_.size();
|
|
327
|
+
mem_ctx_->position = 0;
|
|
302
328
|
|
|
303
329
|
// Allocate AVIO buffer
|
|
304
330
|
uint8_t* avio_buffer = static_cast<uint8_t*>(av_malloc(kAVIOBufferSize));
|
|
305
331
|
if (!avio_buffer) {
|
|
306
|
-
delete
|
|
332
|
+
delete mem_ctx_;
|
|
333
|
+
mem_ctx_ = nullptr;
|
|
307
334
|
return false;
|
|
308
335
|
}
|
|
309
336
|
|
|
310
337
|
// Create custom AVIO context
|
|
311
|
-
avio_context_ = avio_alloc_context(avio_buffer, kAVIOBufferSize, 0,
|
|
338
|
+
avio_context_ = avio_alloc_context(avio_buffer, kAVIOBufferSize, 0, mem_ctx_,
|
|
312
339
|
ReadPacket, nullptr, SeekPacket);
|
|
313
340
|
if (!avio_context_) {
|
|
314
341
|
av_free(avio_buffer);
|
|
315
|
-
delete
|
|
342
|
+
delete mem_ctx_;
|
|
343
|
+
mem_ctx_ = nullptr;
|
|
316
344
|
return false;
|
|
317
345
|
}
|
|
318
346
|
|
|
@@ -321,7 +349,8 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
321
349
|
if (!format_context_) {
|
|
322
350
|
av_freep(&avio_context_->buffer);
|
|
323
351
|
avio_context_free(&avio_context_);
|
|
324
|
-
delete
|
|
352
|
+
delete mem_ctx_;
|
|
353
|
+
mem_ctx_ = nullptr;
|
|
325
354
|
return false;
|
|
326
355
|
}
|
|
327
356
|
|
|
@@ -345,7 +374,8 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
345
374
|
av_freep(&avio_context_->buffer);
|
|
346
375
|
avio_context_free(&avio_context_);
|
|
347
376
|
avio_context_ = nullptr;
|
|
348
|
-
delete
|
|
377
|
+
delete mem_ctx_;
|
|
378
|
+
mem_ctx_ = nullptr;
|
|
349
379
|
return false;
|
|
350
380
|
}
|
|
351
381
|
|
|
@@ -356,7 +386,8 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
356
386
|
av_freep(&avio_context_->buffer);
|
|
357
387
|
avio_context_free(&avio_context_);
|
|
358
388
|
avio_context_ = nullptr;
|
|
359
|
-
delete
|
|
389
|
+
delete mem_ctx_;
|
|
390
|
+
mem_ctx_ = nullptr;
|
|
360
391
|
return false;
|
|
361
392
|
}
|
|
362
393
|
|
|
@@ -375,7 +406,8 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
375
406
|
av_freep(&avio_context_->buffer);
|
|
376
407
|
avio_context_free(&avio_context_);
|
|
377
408
|
avio_context_ = nullptr;
|
|
378
|
-
delete
|
|
409
|
+
delete mem_ctx_;
|
|
410
|
+
mem_ctx_ = nullptr;
|
|
379
411
|
return false;
|
|
380
412
|
}
|
|
381
413
|
|
|
@@ -393,57 +425,58 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
393
425
|
av_freep(&avio_context_->buffer);
|
|
394
426
|
avio_context_free(&avio_context_);
|
|
395
427
|
avio_context_ = nullptr;
|
|
396
|
-
delete
|
|
428
|
+
delete mem_ctx_;
|
|
429
|
+
mem_ctx_ = nullptr;
|
|
397
430
|
return false;
|
|
398
431
|
}
|
|
399
432
|
|
|
400
|
-
// Allocate new codec context for the stream
|
|
401
|
-
|
|
433
|
+
// Allocate new codec context for the stream (RAII managed)
|
|
434
|
+
ffmpeg::AVCodecContextPtr stream_codec_ctx =
|
|
435
|
+
ffmpeg::make_codec_context(stream_codec);
|
|
402
436
|
if (!stream_codec_ctx) {
|
|
403
437
|
avformat_close_input(&format_context_);
|
|
404
438
|
av_freep(&avio_context_->buffer);
|
|
405
439
|
avio_context_free(&avio_context_);
|
|
406
440
|
avio_context_ = nullptr;
|
|
407
|
-
delete
|
|
441
|
+
delete mem_ctx_;
|
|
442
|
+
mem_ctx_ = nullptr;
|
|
408
443
|
return false;
|
|
409
444
|
}
|
|
410
445
|
|
|
411
446
|
// Copy codec parameters
|
|
412
|
-
ret = avcodec_parameters_to_context(stream_codec_ctx, codecpar);
|
|
447
|
+
ret = avcodec_parameters_to_context(stream_codec_ctx.get(), codecpar);
|
|
413
448
|
if (ret < 0) {
|
|
414
|
-
avcodec_free_context(&stream_codec_ctx);
|
|
415
449
|
avformat_close_input(&format_context_);
|
|
416
450
|
av_freep(&avio_context_->buffer);
|
|
417
451
|
avio_context_free(&avio_context_);
|
|
418
452
|
avio_context_ = nullptr;
|
|
419
|
-
delete
|
|
453
|
+
delete mem_ctx_;
|
|
454
|
+
mem_ctx_ = nullptr;
|
|
420
455
|
return false;
|
|
421
456
|
}
|
|
422
457
|
|
|
423
458
|
// Open codec
|
|
424
|
-
ret = avcodec_open2(stream_codec_ctx, stream_codec, nullptr);
|
|
459
|
+
ret = avcodec_open2(stream_codec_ctx.get(), stream_codec, nullptr);
|
|
425
460
|
if (ret < 0) {
|
|
426
|
-
avcodec_free_context(&stream_codec_ctx);
|
|
427
461
|
avformat_close_input(&format_context_);
|
|
428
462
|
av_freep(&avio_context_->buffer);
|
|
429
463
|
avio_context_free(&avio_context_);
|
|
430
464
|
avio_context_ = nullptr;
|
|
431
|
-
delete
|
|
465
|
+
delete mem_ctx_;
|
|
466
|
+
mem_ctx_ = nullptr;
|
|
432
467
|
return false;
|
|
433
468
|
}
|
|
434
469
|
|
|
435
|
-
// Count frames and decode them
|
|
436
|
-
|
|
437
|
-
|
|
470
|
+
// Count frames and decode them (RAII managed)
|
|
471
|
+
ffmpeg::AVPacketPtr pkt = ffmpeg::make_packet();
|
|
472
|
+
ffmpeg::AVFramePtr frm = ffmpeg::make_frame();
|
|
438
473
|
if (!pkt || !frm) {
|
|
439
|
-
if (pkt) av_packet_free(&pkt);
|
|
440
|
-
if (frm) av_frame_free(&frm);
|
|
441
|
-
avcodec_free_context(&stream_codec_ctx);
|
|
442
474
|
avformat_close_input(&format_context_);
|
|
443
475
|
av_freep(&avio_context_->buffer);
|
|
444
476
|
avio_context_free(&avio_context_);
|
|
445
477
|
avio_context_ = nullptr;
|
|
446
|
-
delete
|
|
478
|
+
delete mem_ctx_;
|
|
479
|
+
mem_ctx_ = nullptr;
|
|
447
480
|
return false;
|
|
448
481
|
}
|
|
449
482
|
|
|
@@ -492,13 +525,13 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
492
525
|
}
|
|
493
526
|
|
|
494
527
|
// Read all frames
|
|
495
|
-
while (av_read_frame(format_context_, pkt) >= 0) {
|
|
528
|
+
while (av_read_frame(format_context_, pkt.get()) >= 0) {
|
|
496
529
|
if (pkt->stream_index == video_stream_index_) {
|
|
497
|
-
ret = avcodec_send_packet(stream_codec_ctx, pkt);
|
|
530
|
+
ret = avcodec_send_packet(stream_codec_ctx.get(), pkt.get());
|
|
498
531
|
if (ret >= 0) {
|
|
499
|
-
while (avcodec_receive_frame(stream_codec_ctx, frm) >= 0) {
|
|
532
|
+
while (avcodec_receive_frame(stream_codec_ctx.get(), frm.get()) >= 0) {
|
|
500
533
|
DecodedFrame decoded_frame;
|
|
501
|
-
if (ConvertFrameToRGBA(frm, &decoded_frame.data)) {
|
|
534
|
+
if (ConvertFrameToRGBA(frm.get(), &decoded_frame.data)) {
|
|
502
535
|
decoded_frame.width = frm->width;
|
|
503
536
|
decoded_frame.height = frm->height;
|
|
504
537
|
|
|
@@ -528,18 +561,18 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
528
561
|
decoded_frames_.push_back(std::move(decoded_frame));
|
|
529
562
|
frame_count_++;
|
|
530
563
|
}
|
|
531
|
-
av_frame_unref(frm);
|
|
564
|
+
av_frame_unref(frm.get());
|
|
532
565
|
}
|
|
533
566
|
}
|
|
534
567
|
}
|
|
535
|
-
av_packet_unref(pkt);
|
|
568
|
+
av_packet_unref(pkt.get());
|
|
536
569
|
}
|
|
537
570
|
|
|
538
571
|
// Flush decoder
|
|
539
|
-
avcodec_send_packet(stream_codec_ctx, nullptr);
|
|
540
|
-
while (avcodec_receive_frame(stream_codec_ctx, frm) >= 0) {
|
|
572
|
+
avcodec_send_packet(stream_codec_ctx.get(), nullptr);
|
|
573
|
+
while (avcodec_receive_frame(stream_codec_ctx.get(), frm.get()) >= 0) {
|
|
541
574
|
DecodedFrame decoded_frame;
|
|
542
|
-
if (ConvertFrameToRGBA(frm, &decoded_frame.data)) {
|
|
575
|
+
if (ConvertFrameToRGBA(frm.get(), &decoded_frame.data)) {
|
|
543
576
|
decoded_frame.width = frm->width;
|
|
544
577
|
decoded_frame.height = frm->height;
|
|
545
578
|
AVRational time_base = video_stream->time_base;
|
|
@@ -553,12 +586,10 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
553
586
|
decoded_frames_.push_back(std::move(decoded_frame));
|
|
554
587
|
frame_count_++;
|
|
555
588
|
}
|
|
556
|
-
av_frame_unref(frm);
|
|
589
|
+
av_frame_unref(frm.get());
|
|
557
590
|
}
|
|
558
591
|
|
|
559
|
-
|
|
560
|
-
av_frame_free(&frm);
|
|
561
|
-
avcodec_free_context(&stream_codec_ctx);
|
|
592
|
+
// RAII handles cleanup of pkt, frm, and stream_codec_ctx
|
|
562
593
|
|
|
563
594
|
// Determine if animated based on frame count
|
|
564
595
|
animated_ = frame_count_ > 1;
|
|
@@ -567,7 +598,7 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
567
598
|
if (frame_count_ == 0) {
|
|
568
599
|
frame_count_ = 1;
|
|
569
600
|
animated_ = false;
|
|
570
|
-
|
|
601
|
+
// mem_ctx_ will be cleaned up by Cleanup()
|
|
571
602
|
return false;
|
|
572
603
|
}
|
|
573
604
|
|
|
@@ -578,7 +609,7 @@ bool ImageDecoder::ParseAnimatedImageMetadata() {
|
|
|
578
609
|
decoded_height_ = decoded_frames_[0].height;
|
|
579
610
|
}
|
|
580
611
|
|
|
581
|
-
|
|
612
|
+
// mem_ctx_ stays alive for use by avio_context_, will be cleaned up by Cleanup()
|
|
582
613
|
return true;
|
|
583
614
|
}
|
|
584
615
|
|
|
@@ -606,18 +637,18 @@ bool ImageDecoder::DecodeImage() {
|
|
|
606
637
|
return false;
|
|
607
638
|
}
|
|
608
639
|
|
|
609
|
-
// Set packet data
|
|
640
|
+
// Set packet data - use .get() to access raw pointer from RAII wrapper
|
|
610
641
|
packet_->data = data_.data();
|
|
611
642
|
packet_->size = static_cast<int>(data_.size());
|
|
612
643
|
|
|
613
|
-
// Send packet to decoder
|
|
614
|
-
int ret = avcodec_send_packet(codec_context_, packet_);
|
|
644
|
+
// Send packet to decoder - use .get() for FFmpeg C API
|
|
645
|
+
int ret = avcodec_send_packet(codec_context_.get(), packet_.get());
|
|
615
646
|
if (ret < 0) {
|
|
616
647
|
return false;
|
|
617
648
|
}
|
|
618
649
|
|
|
619
|
-
// Receive decoded frame
|
|
620
|
-
ret = avcodec_receive_frame(codec_context_, frame_);
|
|
650
|
+
// Receive decoded frame - use .get() for FFmpeg C API
|
|
651
|
+
ret = avcodec_receive_frame(codec_context_.get(), frame_.get());
|
|
621
652
|
if (ret < 0) {
|
|
622
653
|
return false;
|
|
623
654
|
}
|
|
@@ -625,11 +656,12 @@ bool ImageDecoder::DecodeImage() {
|
|
|
625
656
|
decoded_width_ = frame_->width;
|
|
626
657
|
decoded_height_ = frame_->height;
|
|
627
658
|
|
|
628
|
-
// Convert to RGBA
|
|
629
|
-
sws_context_
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
659
|
+
// Convert to RGBA (RAII managed)
|
|
660
|
+
sws_context_.reset(sws_getContext(frame_->width, frame_->height,
|
|
661
|
+
static_cast<AVPixelFormat>(frame_->format),
|
|
662
|
+
frame_->width, frame_->height,
|
|
663
|
+
AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr,
|
|
664
|
+
nullptr, nullptr));
|
|
633
665
|
|
|
634
666
|
if (!sws_context_) {
|
|
635
667
|
return false;
|
|
@@ -645,8 +677,13 @@ bool ImageDecoder::DecodeImage() {
|
|
|
645
677
|
int dest_linesize[4] = {frame_->width * 4, 0, 0, 0};
|
|
646
678
|
|
|
647
679
|
// Convert
|
|
648
|
-
sws_scale(sws_context_, frame_->data, frame_->linesize, 0,
|
|
649
|
-
dest_data, dest_linesize);
|
|
680
|
+
sws_scale(sws_context_.get(), frame_->data, frame_->linesize, 0,
|
|
681
|
+
frame_->height, dest_data, dest_linesize);
|
|
682
|
+
|
|
683
|
+
// Apply alpha premultiplication if requested
|
|
684
|
+
if (premultiply_alpha_ == "premultiply") {
|
|
685
|
+
PremultiplyAlpha(decoded_data_.data(), frame_->width, frame_->height);
|
|
686
|
+
}
|
|
650
687
|
|
|
651
688
|
return true;
|
|
652
689
|
}
|
package/src/image_decoder.h
CHANGED
|
@@ -39,6 +39,9 @@ class ImageDecoder : public Napi::ObjectWrap<ImageDecoder> {
|
|
|
39
39
|
explicit ImageDecoder(const Napi::CallbackInfo& info);
|
|
40
40
|
~ImageDecoder();
|
|
41
41
|
|
|
42
|
+
// Static constructor reference for NAPI class registration
|
|
43
|
+
static Napi::FunctionReference constructor_;
|
|
44
|
+
|
|
42
45
|
// Disallow copy and assign.
|
|
43
46
|
ImageDecoder(const ImageDecoder&) = delete;
|
|
44
47
|
ImageDecoder& operator=(const ImageDecoder&) = delete;
|
|
@@ -66,14 +69,15 @@ class ImageDecoder : public Napi::ObjectWrap<ImageDecoder> {
|
|
|
66
69
|
|
|
67
70
|
// FFmpeg state for static image decoding.
|
|
68
71
|
const AVCodec* codec_;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
ffmpeg::AVCodecContextPtr codec_context_;
|
|
73
|
+
ffmpeg::SwsContextPtr sws_context_;
|
|
74
|
+
ffmpeg::AVFramePtr frame_;
|
|
75
|
+
ffmpeg::AVPacketPtr packet_;
|
|
73
76
|
|
|
74
77
|
// FFmpeg state for animated image parsing.
|
|
75
78
|
AVFormatContext* format_context_; // For container parsing
|
|
76
79
|
AVIOContext* avio_context_; // Custom I/O for memory buffer
|
|
80
|
+
struct MemoryBufferContext* mem_ctx_; // Owned, freed in Cleanup()
|
|
77
81
|
int video_stream_index_; // Stream index for video track
|
|
78
82
|
|
|
79
83
|
// Decoded frame data (static images).
|
|
@@ -89,6 +93,9 @@ class ImageDecoder : public Napi::ObjectWrap<ImageDecoder> {
|
|
|
89
93
|
|
|
90
94
|
bool complete_;
|
|
91
95
|
bool closed_;
|
|
96
|
+
|
|
97
|
+
// Premultiply alpha option: "none", "premultiply", or "default"
|
|
98
|
+
std::string premultiply_alpha_;
|
|
92
99
|
};
|
|
93
100
|
|
|
94
101
|
#endif // SRC_IMAGE_DECODER_H_
|
package/src/muxer.cc
CHANGED
|
@@ -71,6 +71,7 @@ Muxer::Muxer(const Napi::CallbackInfo& info)
|
|
|
71
71
|
// Open output file.
|
|
72
72
|
ret = avio_open(&format_context_->pb, filename_.c_str(), AVIO_FLAG_WRITE);
|
|
73
73
|
if (ret < 0) {
|
|
74
|
+
format_context_.reset(); // Explicitly clean up before throw
|
|
74
75
|
char err[AV_ERROR_MAX_STRING_SIZE];
|
|
75
76
|
av_strerror(ret, err, sizeof(err));
|
|
76
77
|
Napi::Error::New(env, std::string("Failed to open output file: ") + err)
|
|
@@ -88,9 +88,10 @@ Napi::Value TestVideoGenerator::Configure(const Napi::CallbackInfo& info) {
|
|
|
88
88
|
return env.Undefined();
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
// Initialize swscale for
|
|
91
|
+
// Initialize swscale for RGB24 -> RGBA conversion
|
|
92
|
+
// Note: testsrc outputs RGB24 by default, not YUV420P
|
|
92
93
|
sws_yuv_to_rgba_.reset(
|
|
93
|
-
sws_getContext(width_, height_,
|
|
94
|
+
sws_getContext(width_, height_, AV_PIX_FMT_RGB24, width_, height_,
|
|
94
95
|
AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr));
|
|
95
96
|
|
|
96
97
|
if (!sws_yuv_to_rgba_) {
|