@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/audio_encoder.cc
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
#include "src/audio_encoder.h"
|
|
5
5
|
|
|
6
|
+
#include <cstdio>
|
|
6
7
|
#include <string>
|
|
7
8
|
#include <vector>
|
|
8
9
|
|
|
@@ -43,6 +44,8 @@ AudioEncoder::AudioEncoder(const Napi::CallbackInfo& info)
|
|
|
43
44
|
number_of_channels_(0),
|
|
44
45
|
timestamp_(0),
|
|
45
46
|
frame_count_(0) {
|
|
47
|
+
// Track active encoder instance
|
|
48
|
+
webcodecs::counterAudioEncoders++;
|
|
46
49
|
Napi::Env env = info.Env();
|
|
47
50
|
|
|
48
51
|
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
@@ -62,9 +65,27 @@ AudioEncoder::AudioEncoder(const Napi::CallbackInfo& info)
|
|
|
62
65
|
error_callback_ = Napi::Persistent(init.Get("error").As<Napi::Function>());
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
AudioEncoder::~AudioEncoder() {
|
|
68
|
+
AudioEncoder::~AudioEncoder() {
|
|
69
|
+
// CRITICAL: Call Cleanup() first to ensure codec context is properly
|
|
70
|
+
// flushed before any further cleanup.
|
|
71
|
+
Cleanup();
|
|
72
|
+
|
|
73
|
+
// Now safe to disable FFmpeg logging.
|
|
74
|
+
webcodecs::ShutdownFFmpegLogging();
|
|
75
|
+
|
|
76
|
+
webcodecs::counterAudioEncoders--;
|
|
77
|
+
}
|
|
66
78
|
|
|
67
79
|
void AudioEncoder::Cleanup() {
|
|
80
|
+
// Flush codec internal buffers BEFORE destroying resources.
|
|
81
|
+
// Audio codecs (opus, aac, mp3) may have internal queued samples. Flushing
|
|
82
|
+
// ensures they're drained before context destruction.
|
|
83
|
+
// CRITICAL: Only flush if codec was successfully opened. avcodec_flush_buffers
|
|
84
|
+
// crashes on an unopened codec context (the internal codec pointer is NULL).
|
|
85
|
+
if (codec_context_ && avcodec_is_open(codec_context_.get())) {
|
|
86
|
+
avcodec_flush_buffers(codec_context_.get());
|
|
87
|
+
}
|
|
88
|
+
|
|
68
89
|
frame_.reset();
|
|
69
90
|
packet_.reset();
|
|
70
91
|
swr_context_.reset();
|
|
@@ -94,6 +115,12 @@ Napi::Value AudioEncoder::Configure(const Napi::CallbackInfo& info) {
|
|
|
94
115
|
codec_id = AV_CODEC_ID_OPUS;
|
|
95
116
|
} else if (codec_str.find("mp4a.40") == 0) {
|
|
96
117
|
codec_id = AV_CODEC_ID_AAC;
|
|
118
|
+
} else if (codec_str == "flac") {
|
|
119
|
+
codec_id = AV_CODEC_ID_FLAC;
|
|
120
|
+
} else if (codec_str == "mp3") {
|
|
121
|
+
codec_id = AV_CODEC_ID_MP3;
|
|
122
|
+
} else if (codec_str == "vorbis") {
|
|
123
|
+
codec_id = AV_CODEC_ID_VORBIS;
|
|
97
124
|
}
|
|
98
125
|
|
|
99
126
|
// Find encoder.
|
|
@@ -133,10 +160,19 @@ Napi::Value AudioEncoder::Configure(const Napi::CallbackInfo& info) {
|
|
|
133
160
|
codec_context_->bit_rate = webcodecs::AttrAsInt64(config, "bitrate", 128000);
|
|
134
161
|
|
|
135
162
|
// Set sample format based on codec.
|
|
136
|
-
//
|
|
163
|
+
// Different codecs require different sample formats:
|
|
164
|
+
// - Opus: non-planar float (flt)
|
|
165
|
+
// - AAC/Vorbis: planar float (fltp)
|
|
166
|
+
// - MP3: planar signed 16-bit (s16p) or planar float (fltp)
|
|
167
|
+
// - FLAC: signed 16-bit (s16) or signed 32-bit (s32)
|
|
137
168
|
if (codec_id == AV_CODEC_ID_OPUS) {
|
|
138
169
|
codec_context_->sample_fmt = AV_SAMPLE_FMT_FLT;
|
|
170
|
+
} else if (codec_id == AV_CODEC_ID_FLAC) {
|
|
171
|
+
codec_context_->sample_fmt = AV_SAMPLE_FMT_S16;
|
|
172
|
+
} else if (codec_id == AV_CODEC_ID_MP3) {
|
|
173
|
+
codec_context_->sample_fmt = AV_SAMPLE_FMT_S16P;
|
|
139
174
|
} else {
|
|
175
|
+
// AAC and Vorbis use planar float
|
|
140
176
|
codec_context_->sample_fmt = AV_SAMPLE_FMT_FLTP;
|
|
141
177
|
}
|
|
142
178
|
|
|
@@ -298,9 +334,9 @@ void AudioEncoder::Close(const Napi::CallbackInfo& info) {
|
|
|
298
334
|
Napi::Value AudioEncoder::Reset(const Napi::CallbackInfo& info) {
|
|
299
335
|
Napi::Env env = info.Env();
|
|
300
336
|
|
|
337
|
+
// W3C spec: reset() is a no-op when closed (don't throw)
|
|
301
338
|
if (state_ == "closed") {
|
|
302
|
-
|
|
303
|
-
"InvalidStateError: Cannot reset closed encoder");
|
|
339
|
+
return env.Undefined();
|
|
304
340
|
}
|
|
305
341
|
|
|
306
342
|
Cleanup();
|
|
@@ -554,6 +590,21 @@ Napi::Value AudioEncoder::IsConfigSupported(const Napi::CallbackInfo& info) {
|
|
|
554
590
|
if (!c) {
|
|
555
591
|
supported = false;
|
|
556
592
|
}
|
|
593
|
+
} else if (codec == "flac") {
|
|
594
|
+
const AVCodec* c = avcodec_find_encoder(AV_CODEC_ID_FLAC);
|
|
595
|
+
if (!c) {
|
|
596
|
+
supported = false;
|
|
597
|
+
}
|
|
598
|
+
} else if (codec == "mp3") {
|
|
599
|
+
const AVCodec* c = avcodec_find_encoder(AV_CODEC_ID_MP3);
|
|
600
|
+
if (!c) {
|
|
601
|
+
supported = false;
|
|
602
|
+
}
|
|
603
|
+
} else if (codec == "vorbis") {
|
|
604
|
+
const AVCodec* c = avcodec_find_encoder(AV_CODEC_ID_VORBIS);
|
|
605
|
+
if (!c) {
|
|
606
|
+
supported = false;
|
|
607
|
+
}
|
|
557
608
|
} else {
|
|
558
609
|
supported = false;
|
|
559
610
|
}
|
package/src/common.cc
CHANGED
|
@@ -13,10 +13,70 @@
|
|
|
13
13
|
|
|
14
14
|
namespace webcodecs {
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
// STATIC DESTRUCTION ORDER FIX: Use heap-allocated "immortal" counters.
|
|
17
|
+
// During process exit, static destructors may run in unpredictable order.
|
|
18
|
+
// Codec destructors access these counters, so we must ensure they're never
|
|
19
|
+
// destroyed. We trade a tiny memory leak at exit for crash-free shutdown.
|
|
20
|
+
// This matches the pattern used for FFmpeg logging queue/mutex.
|
|
21
|
+
|
|
22
|
+
// Per-class instance counters for deterministic leak detection
|
|
23
|
+
static std::atomic<int64_t>& GetCounterVideoFrames() {
|
|
24
|
+
static auto* counter = new std::atomic<int64_t>(0);
|
|
25
|
+
return *counter;
|
|
26
|
+
}
|
|
27
|
+
static std::atomic<int64_t>& GetCounterAudioData() {
|
|
28
|
+
static auto* counter = new std::atomic<int64_t>(0);
|
|
29
|
+
return *counter;
|
|
30
|
+
}
|
|
31
|
+
static std::atomic<int64_t>& GetCounterVideoEncoders() {
|
|
32
|
+
static auto* counter = new std::atomic<int64_t>(0);
|
|
33
|
+
return *counter;
|
|
34
|
+
}
|
|
35
|
+
static std::atomic<int64_t>& GetCounterVideoDecoders() {
|
|
36
|
+
static auto* counter = new std::atomic<int64_t>(0);
|
|
37
|
+
return *counter;
|
|
38
|
+
}
|
|
39
|
+
static std::atomic<int64_t>& GetCounterAudioEncoders() {
|
|
40
|
+
static auto* counter = new std::atomic<int64_t>(0);
|
|
41
|
+
return *counter;
|
|
42
|
+
}
|
|
43
|
+
static std::atomic<int64_t>& GetCounterAudioDecoders() {
|
|
44
|
+
static auto* counter = new std::atomic<int64_t>(0);
|
|
45
|
+
return *counter;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Legacy counters (maintained for backwards compatibility)
|
|
49
|
+
static std::atomic<int>& GetCounterQueue() {
|
|
50
|
+
static auto* counter = new std::atomic<int>(0);
|
|
51
|
+
return *counter;
|
|
52
|
+
}
|
|
53
|
+
static std::atomic<int>& GetCounterProcess() {
|
|
54
|
+
static auto* counter = new std::atomic<int>(0);
|
|
55
|
+
return *counter;
|
|
56
|
+
}
|
|
57
|
+
static std::atomic<int>& GetCounterFrames() {
|
|
58
|
+
static auto* counter = new std::atomic<int>(0);
|
|
59
|
+
return *counter;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// References to immortal counters for extern linkage
|
|
63
|
+
std::atomic<int64_t>& counterVideoFrames = GetCounterVideoFrames();
|
|
64
|
+
std::atomic<int64_t>& counterAudioData = GetCounterAudioData();
|
|
65
|
+
std::atomic<int64_t>& counterVideoEncoders = GetCounterVideoEncoders();
|
|
66
|
+
std::atomic<int64_t>& counterVideoDecoders = GetCounterVideoDecoders();
|
|
67
|
+
std::atomic<int64_t>& counterAudioEncoders = GetCounterAudioEncoders();
|
|
68
|
+
std::atomic<int64_t>& counterAudioDecoders = GetCounterAudioDecoders();
|
|
69
|
+
|
|
70
|
+
std::atomic<int>& counterQueue = GetCounterQueue();
|
|
71
|
+
std::atomic<int>& counterProcess = GetCounterProcess();
|
|
72
|
+
std::atomic<int>& counterFrames = GetCounterFrames();
|
|
73
|
+
|
|
74
|
+
// FreeCallback for consistent buffer deallocation (following sharp pattern).
|
|
75
|
+
// Default implementation uses delete[]. Can be overridden for platform-specific
|
|
76
|
+
// memory management (e.g., Windows mixed runtime scenarios).
|
|
77
|
+
std::function<void(void*, uint8_t*)> FreeCallback = [](void*, uint8_t* data) {
|
|
78
|
+
delete[] data;
|
|
79
|
+
};
|
|
20
80
|
|
|
21
81
|
//==============================================================================
|
|
22
82
|
// Attribute Helpers
|
|
@@ -276,8 +336,14 @@ Napi::Error FFmpegError(Napi::Env env, const std::string& operation,
|
|
|
276
336
|
}
|
|
277
337
|
|
|
278
338
|
std::string FFmpegErrorString(int errnum) {
|
|
279
|
-
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
|
280
|
-
av_strerror(errnum, errbuf, sizeof(errbuf));
|
|
339
|
+
char errbuf[AV_ERROR_MAX_STRING_SIZE] = {0};
|
|
340
|
+
int ret = av_strerror(errnum, errbuf, sizeof(errbuf));
|
|
341
|
+
// Check for explicit failure OR empty buffer (ABI mismatch with strerror_r).
|
|
342
|
+
// FFmpeg built on musl expects XSI strerror_r (returns int, writes to buffer).
|
|
343
|
+
// When running on glibc, GNU strerror_r returns char* without writing to buffer.
|
|
344
|
+
if (ret < 0 || errbuf[0] == '\0') {
|
|
345
|
+
snprintf(errbuf, sizeof(errbuf), "Error code %d", errnum);
|
|
346
|
+
}
|
|
281
347
|
return std::string(errbuf);
|
|
282
348
|
}
|
|
283
349
|
|
|
@@ -333,6 +399,15 @@ std::string PixelFormatToString(AVPixelFormat format) {
|
|
|
333
399
|
}
|
|
334
400
|
}
|
|
335
401
|
|
|
402
|
+
//==============================================================================
|
|
403
|
+
// String Utilities
|
|
404
|
+
//==============================================================================
|
|
405
|
+
|
|
406
|
+
std::string TrimEnd(const std::string& str) {
|
|
407
|
+
size_t end = str.find_last_not_of(" \t\n\r\f\v");
|
|
408
|
+
return (end == std::string::npos) ? "" : str.substr(0, end + 1);
|
|
409
|
+
}
|
|
410
|
+
|
|
336
411
|
//==============================================================================
|
|
337
412
|
// FFmpeg Initialization
|
|
338
413
|
//==============================================================================
|
|
@@ -351,13 +426,31 @@ void InitFFmpeg() {
|
|
|
351
426
|
// FFmpeg Logging
|
|
352
427
|
//==============================================================================
|
|
353
428
|
|
|
354
|
-
|
|
355
|
-
|
|
429
|
+
// STATIC DESTRUCTION ORDER FIX: Use heap-allocated "immortal" objects to prevent
|
|
430
|
+
// crashes during process exit. When vitest worker processes exit, static
|
|
431
|
+
// destructors may run in unpredictable order, causing FFmpeg's log callback
|
|
432
|
+
// to access destroyed mutex/queue. By never destroying these, we trade a tiny
|
|
433
|
+
// memory leak at exit for crash-free shutdown.
|
|
434
|
+
static std::queue<std::string>& GetWarningsQueue() {
|
|
435
|
+
static auto* queue = new std::queue<std::string>();
|
|
436
|
+
return *queue;
|
|
437
|
+
}
|
|
438
|
+
static std::mutex& GetWarningsMutex() {
|
|
439
|
+
static auto* mutex = new std::mutex();
|
|
440
|
+
return *mutex;
|
|
441
|
+
}
|
|
442
|
+
static std::atomic<bool> ffmpegLoggingActive{false};
|
|
356
443
|
|
|
357
444
|
void InitFFmpegLogging() {
|
|
358
445
|
static std::once_flag log_init_once;
|
|
359
446
|
std::call_once(log_init_once, []() {
|
|
447
|
+
ffmpegLoggingActive.store(true, std::memory_order_release);
|
|
360
448
|
av_log_set_callback([](void* ptr, int level, const char* fmt, va_list vl) {
|
|
449
|
+
// Guard against callbacks during/after shutdown to prevent
|
|
450
|
+
// static destruction order fiasco on process exit.
|
|
451
|
+
if (!ffmpegLoggingActive.load(std::memory_order_acquire)) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
361
454
|
if (level <= AV_LOG_WARNING) {
|
|
362
455
|
char buf[1024];
|
|
363
456
|
vsnprintf(buf, sizeof(buf), fmt, vl);
|
|
@@ -369,28 +462,43 @@ void InitFFmpegLogging() {
|
|
|
369
462
|
// Skip empty messages
|
|
370
463
|
if (strlen(buf) == 0) return;
|
|
371
464
|
|
|
372
|
-
std::lock_guard<std::mutex> lock(
|
|
373
|
-
|
|
465
|
+
std::lock_guard<std::mutex> lock(GetWarningsMutex());
|
|
466
|
+
GetWarningsQueue().push(buf);
|
|
374
467
|
}
|
|
375
468
|
});
|
|
376
469
|
av_log_set_level(AV_LOG_WARNING);
|
|
377
470
|
});
|
|
378
471
|
}
|
|
379
472
|
|
|
473
|
+
void ShutdownFFmpegLogging() {
|
|
474
|
+
// Ensure shutdown runs exactly once - multiple concurrent calls from
|
|
475
|
+
// encoder/decoder destructors and cleanup hook could race on av_log_set_callback.
|
|
476
|
+
static std::once_flag shutdown_once;
|
|
477
|
+
std::call_once(shutdown_once, []() {
|
|
478
|
+
// Disable logging callback before static destructors run.
|
|
479
|
+
// This prevents the callback from accessing destroyed statics
|
|
480
|
+
// during process exit (static destruction order fiasco).
|
|
481
|
+
ffmpegLoggingActive.store(false, std::memory_order_release);
|
|
482
|
+
av_log_set_callback(nullptr);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
380
486
|
std::vector<std::string> GetFFmpegWarnings() {
|
|
381
|
-
std::lock_guard<std::mutex> lock(
|
|
487
|
+
std::lock_guard<std::mutex> lock(GetWarningsMutex());
|
|
382
488
|
std::vector<std::string> result;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
489
|
+
auto& queue = GetWarningsQueue();
|
|
490
|
+
while (!queue.empty()) {
|
|
491
|
+
result.push_back(queue.front());
|
|
492
|
+
queue.pop();
|
|
386
493
|
}
|
|
387
494
|
return result;
|
|
388
495
|
}
|
|
389
496
|
|
|
390
497
|
void ClearFFmpegWarnings() {
|
|
391
|
-
std::lock_guard<std::mutex> lock(
|
|
392
|
-
|
|
393
|
-
|
|
498
|
+
std::lock_guard<std::mutex> lock(GetWarningsMutex());
|
|
499
|
+
auto& queue = GetWarningsQueue();
|
|
500
|
+
while (!queue.empty()) {
|
|
501
|
+
queue.pop();
|
|
394
502
|
}
|
|
395
503
|
}
|
|
396
504
|
|
package/src/common.h
CHANGED
|
@@ -14,6 +14,7 @@ extern "C" {
|
|
|
14
14
|
#include <napi.h>
|
|
15
15
|
|
|
16
16
|
#include <atomic>
|
|
17
|
+
#include <functional>
|
|
17
18
|
#include <mutex>
|
|
18
19
|
#include <string>
|
|
19
20
|
#include <tuple>
|
|
@@ -109,12 +110,40 @@ AVPixelFormat PixelFormatFromString(const std::string& format);
|
|
|
109
110
|
std::string PixelFormatToString(AVPixelFormat format);
|
|
110
111
|
|
|
111
112
|
//==============================================================================
|
|
112
|
-
// Global Counters (for monitoring
|
|
113
|
+
// Global Counters (for monitoring and leak detection)
|
|
113
114
|
//==============================================================================
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
// Per-class instance counters for deterministic leak detection.
|
|
117
|
+
// Increment in constructor, decrement in destructor.
|
|
118
|
+
// NOTE: These are references to heap-allocated "immortal" atomics to prevent
|
|
119
|
+
// static destruction order issues during process exit (darwin-x64 fix).
|
|
120
|
+
extern std::atomic<int64_t>& counterVideoFrames;
|
|
121
|
+
extern std::atomic<int64_t>& counterAudioData;
|
|
122
|
+
extern std::atomic<int64_t>& counterVideoEncoders;
|
|
123
|
+
extern std::atomic<int64_t>& counterVideoDecoders;
|
|
124
|
+
extern std::atomic<int64_t>& counterAudioEncoders;
|
|
125
|
+
extern std::atomic<int64_t>& counterAudioDecoders;
|
|
126
|
+
|
|
127
|
+
// Legacy counters (maintained for backwards compatibility)
|
|
128
|
+
extern std::atomic<int>& counterQueue;
|
|
129
|
+
extern std::atomic<int>& counterProcess;
|
|
130
|
+
extern std::atomic<int>& counterFrames; // Legacy frame counter (use counterVideoFrames)
|
|
131
|
+
|
|
132
|
+
//==============================================================================
|
|
133
|
+
// Memory Management (following sharp pattern for Windows compatibility)
|
|
134
|
+
//==============================================================================
|
|
135
|
+
|
|
136
|
+
// FreeCallback for consistent buffer deallocation across platforms.
|
|
137
|
+
// Windows mixed runtime libraries can have issues with different allocators,
|
|
138
|
+
// so using a consistent deallocation function avoids potential crashes.
|
|
139
|
+
extern std::function<void(void*, uint8_t*)> FreeCallback;
|
|
140
|
+
|
|
141
|
+
//==============================================================================
|
|
142
|
+
// String Utilities
|
|
143
|
+
//==============================================================================
|
|
144
|
+
|
|
145
|
+
// Trim whitespace from end of string (following sharp pattern)
|
|
146
|
+
std::string TrimEnd(const std::string& str);
|
|
118
147
|
|
|
119
148
|
//==============================================================================
|
|
120
149
|
// FFmpeg Initialization
|
|
@@ -127,6 +156,7 @@ void InitFFmpeg();
|
|
|
127
156
|
//==============================================================================
|
|
128
157
|
|
|
129
158
|
void InitFFmpegLogging();
|
|
159
|
+
void ShutdownFFmpegLogging();
|
|
130
160
|
std::vector<std::string> GetFFmpegWarnings();
|
|
131
161
|
void ClearFFmpegWarnings();
|
|
132
162
|
|
package/src/demuxer.cc
CHANGED
|
@@ -20,6 +20,7 @@ Napi::Object Demuxer::Init(Napi::Env env, Napi::Object exports) {
|
|
|
20
20
|
{
|
|
21
21
|
InstanceMethod("open", &Demuxer::Open),
|
|
22
22
|
InstanceMethod("demux", &Demuxer::DemuxPackets),
|
|
23
|
+
InstanceMethod("demuxPackets", &Demuxer::DemuxPackets),
|
|
23
24
|
InstanceMethod("close", &Demuxer::Close),
|
|
24
25
|
InstanceMethod("getVideoTrack", &Demuxer::GetVideoTrack),
|
|
25
26
|
InstanceMethod("getAudioTrack", &Demuxer::GetAudioTrack),
|
|
@@ -65,6 +66,11 @@ void Demuxer::Cleanup() {
|
|
|
65
66
|
tracks_.clear();
|
|
66
67
|
video_stream_index_ = -1;
|
|
67
68
|
audio_stream_index_ = -1;
|
|
69
|
+
|
|
70
|
+
// Clear callback references
|
|
71
|
+
on_track_callback_.Reset();
|
|
72
|
+
on_chunk_callback_.Reset();
|
|
73
|
+
on_error_callback_.Reset();
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
Napi::Value Demuxer::Open(const Napi::CallbackInfo& info) {
|
|
@@ -175,6 +181,11 @@ Napi::Value Demuxer::DemuxPackets(const Napi::CallbackInfo& info) {
|
|
|
175
181
|
return env.Undefined();
|
|
176
182
|
}
|
|
177
183
|
|
|
184
|
+
int max_packets = 0; // 0 = unlimited (backwards compatible)
|
|
185
|
+
if (info.Length() > 0 && info[0].IsNumber()) {
|
|
186
|
+
max_packets = info[0].As<Napi::Number>().Int32Value();
|
|
187
|
+
}
|
|
188
|
+
|
|
178
189
|
ffmpeg::AVPacketPtr packet = ffmpeg::make_packet();
|
|
179
190
|
if (!packet) {
|
|
180
191
|
Napi::Error::New(env, "Failed to allocate packet")
|
|
@@ -182,15 +193,18 @@ Napi::Value Demuxer::DemuxPackets(const Napi::CallbackInfo& info) {
|
|
|
182
193
|
return env.Undefined();
|
|
183
194
|
}
|
|
184
195
|
|
|
185
|
-
|
|
196
|
+
int packets_read = 0;
|
|
197
|
+
while ((max_packets == 0 || packets_read < max_packets) &&
|
|
198
|
+
av_read_frame(format_context_.get(), packet.get()) >= 0) {
|
|
186
199
|
if (packet->stream_index == video_stream_index_ ||
|
|
187
200
|
packet->stream_index == audio_stream_index_) {
|
|
188
201
|
EmitChunk(env, packet.get(), packet->stream_index);
|
|
202
|
+
packets_read++;
|
|
189
203
|
}
|
|
190
204
|
av_packet_unref(packet.get());
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
return env
|
|
207
|
+
return Napi::Number::New(env, packets_read);
|
|
194
208
|
}
|
|
195
209
|
|
|
196
210
|
void Demuxer::EmitChunk(Napi::Env env, AVPacket* packet, int track_index) {
|
|
@@ -26,6 +26,7 @@ Napi::Object EncodedAudioChunk::Init(Napi::Env env, Napi::Object exports) {
|
|
|
26
26
|
InstanceAccessor("byteLength", &EncodedAudioChunk::GetByteLength,
|
|
27
27
|
nullptr),
|
|
28
28
|
InstanceMethod("copyTo", &EncodedAudioChunk::CopyTo),
|
|
29
|
+
InstanceMethod("close", &EncodedAudioChunk::Close),
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
constructor_ = Napi::Persistent(func);
|
|
@@ -173,3 +174,12 @@ void EncodedAudioChunk::CopyTo(const Napi::CallbackInfo& info) {
|
|
|
173
174
|
|
|
174
175
|
std::memcpy(dest_data, data_.data(), data_.size());
|
|
175
176
|
}
|
|
177
|
+
|
|
178
|
+
void EncodedAudioChunk::Close(const Napi::CallbackInfo& info) {
|
|
179
|
+
if (!closed_) {
|
|
180
|
+
// clear() + shrink_to_fit() actually releases memory.
|
|
181
|
+
data_.clear();
|
|
182
|
+
data_.shrink_to_fit();
|
|
183
|
+
closed_ = true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -30,6 +30,7 @@ class EncodedAudioChunk : public Napi::ObjectWrap<EncodedAudioChunk> {
|
|
|
30
30
|
|
|
31
31
|
// Methods.
|
|
32
32
|
void CopyTo(const Napi::CallbackInfo& info);
|
|
33
|
+
void Close(const Napi::CallbackInfo& info);
|
|
33
34
|
|
|
34
35
|
// Internal access.
|
|
35
36
|
const std::vector<uint8_t>& GetData() const { return data_; }
|
|
@@ -41,6 +42,7 @@ class EncodedAudioChunk : public Napi::ObjectWrap<EncodedAudioChunk> {
|
|
|
41
42
|
int64_t timestamp_;
|
|
42
43
|
int64_t duration_;
|
|
43
44
|
std::vector<uint8_t> data_;
|
|
45
|
+
bool closed_ = false;
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
#endif // SRC_ENCODED_AUDIO_CHUNK_H_
|
|
@@ -28,6 +28,7 @@ class EncodedVideoChunk : public Napi::ObjectWrap<EncodedVideoChunk> {
|
|
|
28
28
|
const uint8_t* GetData() const { return data_.data(); }
|
|
29
29
|
size_t GetDataSize() const { return data_.size(); }
|
|
30
30
|
int64_t GetTimestampValue() const { return timestamp_; }
|
|
31
|
+
int64_t GetDurationValue() const { return has_duration_ ? duration_ : 0; }
|
|
31
32
|
const std::string& GetTypeValue() const { return type_; }
|
|
32
33
|
|
|
33
34
|
private:
|
package/src/error_builder.cc
CHANGED
|
@@ -20,10 +20,6 @@ Napi::Object ErrorBuilder::Init(Napi::Env env, Napi::Object exports) {
|
|
|
20
20
|
InstanceMethod("throwError", &ErrorBuilder::ThrowErrorJS),
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
Napi::FunctionReference* constructor = new Napi::FunctionReference();
|
|
24
|
-
*constructor = Napi::Persistent(func);
|
|
25
|
-
env.SetInstanceData(constructor);
|
|
26
|
-
|
|
27
23
|
exports.Set("ErrorBuilder", func);
|
|
28
24
|
return exports;
|
|
29
25
|
}
|