@pproenca/node-webcodecs 0.1.1-alpha.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.
Files changed (97) hide show
  1. package/README.md +75 -233
  2. package/binding.gyp +123 -0
  3. package/dist/audio-decoder.js +1 -2
  4. package/dist/audio-encoder.d.ts +4 -0
  5. package/dist/audio-encoder.js +28 -2
  6. package/dist/binding.d.ts +0 -2
  7. package/dist/binding.js +43 -125
  8. package/dist/control-message-queue.js +0 -1
  9. package/dist/demuxer.d.ts +7 -0
  10. package/dist/demuxer.js +9 -0
  11. package/dist/encoded-chunks.d.ts +16 -0
  12. package/dist/encoded-chunks.js +82 -2
  13. package/dist/image-decoder.js +4 -0
  14. package/dist/index.d.ts +11 -0
  15. package/dist/index.js +3 -1
  16. package/dist/native-types.d.ts +20 -0
  17. package/dist/platform.d.ts +1 -10
  18. package/dist/platform.js +1 -39
  19. package/dist/resource-manager.d.ts +1 -2
  20. package/dist/resource-manager.js +3 -17
  21. package/dist/types.d.ts +12 -0
  22. package/dist/video-decoder.d.ts +21 -0
  23. package/dist/video-decoder.js +74 -2
  24. package/dist/video-encoder.d.ts +22 -0
  25. package/dist/video-encoder.js +83 -8
  26. package/lib/audio-decoder.ts +1 -2
  27. package/lib/audio-encoder.ts +31 -2
  28. package/lib/binding.ts +45 -104
  29. package/lib/control-message-queue.ts +0 -1
  30. package/lib/demuxer.ts +10 -0
  31. package/lib/encoded-chunks.ts +90 -2
  32. package/lib/image-decoder.ts +5 -0
  33. package/lib/index.ts +3 -0
  34. package/lib/native-types.ts +22 -0
  35. package/lib/platform.ts +1 -41
  36. package/lib/resource-manager.ts +3 -19
  37. package/lib/types.ts +13 -0
  38. package/lib/video-decoder.ts +84 -2
  39. package/lib/video-encoder.ts +90 -8
  40. package/package.json +49 -32
  41. package/src/addon.cc +57 -0
  42. package/src/async_decode_worker.cc +241 -33
  43. package/src/async_decode_worker.h +55 -3
  44. package/src/async_encode_worker.cc +103 -35
  45. package/src/async_encode_worker.h +23 -4
  46. package/src/audio_data.cc +38 -15
  47. package/src/audio_data.h +1 -0
  48. package/src/audio_decoder.cc +24 -3
  49. package/src/audio_encoder.cc +55 -4
  50. package/src/common.cc +125 -17
  51. package/src/common.h +34 -4
  52. package/src/demuxer.cc +16 -2
  53. package/src/encoded_audio_chunk.cc +10 -0
  54. package/src/encoded_audio_chunk.h +2 -0
  55. package/src/encoded_video_chunk.h +1 -0
  56. package/src/error_builder.cc +0 -4
  57. package/src/image_decoder.cc +127 -90
  58. package/src/image_decoder.h +11 -4
  59. package/src/muxer.cc +1 -0
  60. package/src/test_video_generator.cc +3 -2
  61. package/src/video_decoder.cc +169 -19
  62. package/src/video_decoder.h +9 -11
  63. package/src/video_encoder.cc +389 -32
  64. package/src/video_encoder.h +15 -0
  65. package/src/video_filter.cc +22 -11
  66. package/src/video_frame.cc +160 -5
  67. package/src/warnings.cc +0 -4
  68. package/dist/audio-data.js.map +0 -1
  69. package/dist/audio-decoder.js.map +0 -1
  70. package/dist/audio-encoder.js.map +0 -1
  71. package/dist/binding.js.map +0 -1
  72. package/dist/codec-base.js.map +0 -1
  73. package/dist/control-message-queue.js.map +0 -1
  74. package/dist/demuxer.js.map +0 -1
  75. package/dist/encoded-chunks.js.map +0 -1
  76. package/dist/errors.js.map +0 -1
  77. package/dist/ffmpeg.d.ts +0 -21
  78. package/dist/ffmpeg.js +0 -112
  79. package/dist/image-decoder.js.map +0 -1
  80. package/dist/image-track-list.js.map +0 -1
  81. package/dist/image-track.js.map +0 -1
  82. package/dist/index.js.map +0 -1
  83. package/dist/is.js.map +0 -1
  84. package/dist/muxer.js.map +0 -1
  85. package/dist/native-types.js.map +0 -1
  86. package/dist/platform.js.map +0 -1
  87. package/dist/resource-manager.js.map +0 -1
  88. package/dist/test-video-generator.js.map +0 -1
  89. package/dist/transfer.js.map +0 -1
  90. package/dist/types.js.map +0 -1
  91. package/dist/video-decoder.js.map +0 -1
  92. package/dist/video-encoder.js.map +0 -1
  93. package/dist/video-filter.js.map +0 -1
  94. package/dist/video-frame.js.map +0 -1
  95. package/install/build.js +0 -51
  96. package/install/check.js +0 -192
  97. package/lib/ffmpeg.ts +0 -78
@@ -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
- Napi::FunctionReference* constructor = new Napi::FunctionReference();
81
- *constructor = Napi::Persistent(func);
82
- env.SetInstanceData(constructor);
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_(nullptr),
92
- sws_context_(nullptr),
93
- frame_(nullptr),
94
- packet_(nullptr),
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_ = avcodec_alloc_context3(codec_);
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_ = av_frame_alloc();
179
- packet_ = av_packet_alloc();
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
- if (sws_context_) {
209
- sws_freeContext(sws_context_);
210
- sws_context_ = nullptr;
211
- }
212
- if (frame_) {
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
- SwsContext* local_sws =
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
- sws_freeContext(local_sws);
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
- MemoryBufferContext* mem_ctx = new MemoryBufferContext();
299
- mem_ctx->data = data_.data();
300
- mem_ctx->size = data_.size();
301
- mem_ctx->position = 0;
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 mem_ctx;
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, mem_ctx,
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 mem_ctx;
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 mem_ctx;
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 mem_ctx;
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 mem_ctx;
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 mem_ctx;
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 mem_ctx;
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
- AVCodecContext* stream_codec_ctx = avcodec_alloc_context3(stream_codec);
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 mem_ctx;
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 mem_ctx;
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 mem_ctx;
465
+ delete mem_ctx_;
466
+ mem_ctx_ = nullptr;
432
467
  return false;
433
468
  }
434
469
 
435
- // Count frames and decode them
436
- AVPacket* pkt = av_packet_alloc();
437
- AVFrame* frm = av_frame_alloc();
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 mem_ctx;
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
- av_packet_free(&pkt);
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
- delete mem_ctx;
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
- delete mem_ctx;
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_ = sws_getContext(frame_->width, frame_->height,
630
- static_cast<AVPixelFormat>(frame_->format),
631
- frame_->width, frame_->height, AV_PIX_FMT_RGBA,
632
- SWS_BILINEAR, nullptr, nullptr, nullptr);
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, frame_->height,
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
  }
@@ -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
- AVCodecContext* codec_context_;
70
- SwsContext* sws_context_;
71
- AVFrame* frame_;
72
- AVPacket* packet_;
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 YUV420P -> RGBA conversion
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_, AV_PIX_FMT_YUV420P, 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_) {