Skip to content

Commit 0f9a96b

Browse files
committed
Fix all remaining audio codec tests (84/84 passing)
Audio encoder improvements: - FLAC: Generate proper fLaC header with STREAMINFO metadata block - MP3: Auto-downmix to stereo when >2 channels (MP3 only supports mono/stereo) - Vorbis: Explicit FLTP sample format handling - Timestamps: Clamp negative timestamps to 0 - Better error messages: Include codec params in failure messages
1 parent 568bb86 commit 0f9a96b

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

.beads/issues.jsonl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{"id":"webcodecs-node-4ub","title":"Create troubleshooting/overview.mdx","description":"Troubleshooting hub with common errors, installation issues, memory leaks, performance tips","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-03T23:41:02.251818-06:00","updated_at":"2025-12-03T23:41:02.251818-06:00"}
55
{"id":"webcodecs-node-4xx","title":"Create cookbook/thumbnails.mdx","description":"Recipe: Extract video thumbnails at specific timestamps","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-03T23:40:47.448285-06:00","updated_at":"2025-12-03T23:40:47.448285-06:00"}
66
{"id":"webcodecs-node-57b","title":"Create examples/alpha-video.mdx","description":"Alpha channel video encoding example with VP8/VP9","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-03T23:40:54.924694-06:00","updated_at":"2025-12-03T23:40:54.924694-06:00"}
7-
{"id":"webcodecs-node-6qw","title":"Set up Mintlify documentation structure","description":"Initialize Mintlify docs in the webcodecs-node repo:\n- Create docs/ directory with mint.json\n- Set up navigation structure \n- Configure theme colors (#00ffc8)\n- Create initial page structure","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-03T21:49:58.259928-06:00","updated_at":"2025-12-03T21:59:29.979253-06:00","closed_at":"2025-12-03T21:59:29.979509-06:00","dependencies":[{"issue_id":"webcodecs-node-6qw","depends_on_id":"webcodecs-node-nhw","type":"blocks","created_at":"2025-12-03T21:49:58.260839-06:00","created_by":"daemon"}]}
7+
{"id":"webcodecs-node-6qw","title":"Set up Mintlify documentation structure","description":"Initialize Mintlify docs in the webcodecs-node repo:\n- Create docs/ directory with mint.json\n- Set up navigation structure \n- Configure theme colors (#00ffc8)\n- Create initial page structure","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-03T21:49:58.259928-06:00","updated_at":"2025-12-03T21:59:29.979253-06:00","closed_at":"2025-12-03T21:59:29.979509-06:00","dependencies":[{"issue_id":"webcodecs-node-6qw","depends_on_id":"webcodecs-node-nhw","type":"blocks","created_at":"2025-12-03T21:49:58.260839-06:00","created_by":"daemon","metadata":"{}"}]}
88
{"id":"webcodecs-node-b6h","title":"Create cookbook/webrtc-recording.mdx","description":"Recipe: Record and composite WebRTC streams with re-encoding","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-03T23:40:47.484861-06:00","updated_at":"2025-12-03T23:40:47.484861-06:00"}
99
{"id":"webcodecs-node-br4","title":"Create examples/basic-encoding.mdx","description":"Basic video encoding example with expected output (console, file size, visual)","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-03T23:40:54.843712-06:00","updated_at":"2025-12-03T23:40:54.843712-06:00"}
1010
{"id":"webcodecs-node-e3l","title":"Create cookbook/watermark.mdx","description":"Recipe: Add watermark overlay to videos using node-canvas","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-03T23:40:47.40937-06:00","updated_at":"2025-12-03T23:40:47.40937-06:00"}

native/audio.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "audio.h"
22
#include <cstring>
3+
#include <vector>
34

45
// ==================== AudioDataNative ====================
56

@@ -523,6 +524,12 @@ void AudioEncoderNative::Configure(const Napi::CallbackInfo& info) {
523524
codecCtx_->sample_fmt = AV_SAMPLE_FMT_S16; // FLAC uses s16
524525
} else if (codecName == "libmp3lame") {
525526
codecCtx_->sample_fmt = AV_SAMPLE_FMT_FLTP; // MP3 uses float planar
527+
// MP3 only supports mono or stereo - limit channels if needed
528+
if (channels_ > 2) {
529+
channels_ = 2; // Downmix to stereo
530+
}
531+
} else if (codecName == "libvorbis") {
532+
codecCtx_->sample_fmt = AV_SAMPLE_FMT_FLTP; // Vorbis uses float planar
526533
} else {
527534
// AAC and most others use float planar
528535
codecCtx_->sample_fmt = AV_SAMPLE_FMT_FLTP;
@@ -543,8 +550,11 @@ void AudioEncoderNative::Configure(const Napi::CallbackInfo& info) {
543550
if (ret < 0) {
544551
char errBuf[256];
545552
av_strerror(ret, errBuf, sizeof(errBuf));
553+
std::string errMsg = std::string("Failed to open codec: ") + errBuf +
554+
" (codec=" + codecName + ", sampleRate=" + std::to_string(sampleRate_) +
555+
", channels=" + std::to_string(channels_) + ", bitrate=" + std::to_string(codecCtx_->bit_rate) + ")";
546556
avcodec_free_context(&codecCtx_);
547-
Napi::Error::New(env, std::string("Failed to open codec: ") + errBuf).ThrowAsJavaScriptException();
557+
Napi::Error::New(env, errMsg).ThrowAsJavaScriptException();
548558
return;
549559
}
550560

@@ -651,7 +661,35 @@ void AudioEncoderNative::EmitChunk(Napi::Env env, AVPacket* packet) {
651661

652662
Napi::Value extradataValue = env.Undefined();
653663
if (codecCtx_->extradata && codecCtx_->extradata_size > 0) {
654-
extradataValue = Napi::Buffer<uint8_t>::Copy(env, codecCtx_->extradata, codecCtx_->extradata_size);
664+
// For FLAC, we need to wrap the STREAMINFO with "fLaC" header
665+
// FLAC stream format: "fLaC" + metadata block header + STREAMINFO
666+
if (codec_->id == AV_CODEC_ID_FLAC) {
667+
// Build proper FLAC header: "fLaC" (4) + block header (4) + STREAMINFO (34)
668+
size_t fullSize = 4 + 4 + codecCtx_->extradata_size;
669+
std::vector<uint8_t> flacHeader(fullSize);
670+
671+
// "fLaC" stream marker
672+
flacHeader[0] = 'f';
673+
flacHeader[1] = 'L';
674+
flacHeader[2] = 'a';
675+
flacHeader[3] = 'C';
676+
677+
// Metadata block header: last-flag (1 bit) + type (7 bits) + size (24 bits)
678+
// 0x80 = last block flag, 0x00 = STREAMINFO type
679+
// Combined: 0x80 for "last STREAMINFO block"
680+
flacHeader[4] = 0x80; // Last block flag + STREAMINFO type (0)
681+
// Size in big-endian (3 bytes): typically 34 for STREAMINFO
682+
flacHeader[5] = (codecCtx_->extradata_size >> 16) & 0xFF;
683+
flacHeader[6] = (codecCtx_->extradata_size >> 8) & 0xFF;
684+
flacHeader[7] = codecCtx_->extradata_size & 0xFF;
685+
686+
// Copy STREAMINFO data
687+
memcpy(flacHeader.data() + 8, codecCtx_->extradata, codecCtx_->extradata_size);
688+
689+
extradataValue = Napi::Buffer<uint8_t>::Copy(env, flacHeader.data(), fullSize);
690+
} else {
691+
extradataValue = Napi::Buffer<uint8_t>::Copy(env, codecCtx_->extradata, codecCtx_->extradata_size);
692+
}
655693
}
656694

657695
// WebCodecs spec: output timestamps should match input timestamps (in microseconds).
@@ -664,6 +702,10 @@ void AudioEncoderNative::EmitChunk(Napi::Env env, AVPacket* packet) {
664702
int64_t paddingUs = (int64_t)codecCtx_->initial_padding * 1000000 / codecCtx_->sample_rate;
665703
timestampUs += paddingUs;
666704
}
705+
// Ensure timestamps are never negative (can happen with some codecs)
706+
if (timestampUs < 0) {
707+
timestampUs = 0;
708+
}
667709

668710
outputCallback_.Value().Call({
669711
buffer,

0 commit comments

Comments
 (0)