Closed erwanvivien closed 9 months ago
Hi, thank you and thanks for the report!
Could it be that you're calling finalize
on the muxer multiple times? It seems like your code would allow that, since finalize
is called inside a callback that gets called multiple times. I've looked through my code and can't figure out another way finalizeCurrentChunk
could be called with sampleData
already set to null.
I would advise you to use a different pattern to flush the encoder. Instead of what you're doing right now, simply flush the encoder once you've encoded the last frame (not when the encoding is complete). Flush returns a Promise that will not resolve until all pending frames have finished encoding, so you can safely wait for its resolution and then call finalize
on the muxer.
Thanks for your response!
Instead of what you're doing right now, simply flush the encoder once you've encoded the last frame
I feel like this is what I'm doing 🤔 frameCount
is the decodedFrameCount that I retrieve from the demuxer and only when encodedFrameCount
is above this count I try to trigger the videoEncoder.flush
Do you mean when I've sent all the decodes
?
Something like this:
const muxer = new Mp4Muxer({
target: new FileSystemWritableFileStreamTarget(fileStream),
video: {
codec: 'avc',
width: params.width,
height: params.height,
},
fastStart: false,
});
const encodeFrame = (chunk, meta) => {
muxer.addVideoChunk(chunk, meta, chunk.timestamp) // I'm making adjustments to timestamp
}
const videoEncoder = new VideoEncoder({
output: encodeFrame,
error: (error) => {
console.error('error', error);
},
});
// The videoEncoder.configure()
// All the calls to videoEncoder.encode()
// then await videoEncoder.flush()
// then muxer.finalize() ?
Also it might be me having a race condition, indeed, because I'm having also the AudioEncoder (that I omitted thinking it wasn't relevant) So I have two spots where the muxer.finalize could be
const { finalize: muxerFinalize, encodeFrame, encodeAudio } = muxers[metadata.kind](fileStream, videoConfig, { width: 640, height: 360 });
let encodedFrameCount = 0;
let encodedSampleCount = 0;
let resolve: () => void = () => { }
let reject: (error: DOMException) => void = () => { }
const close = new Promise((res, rej) => {
resolve = res
reject = rej
})
const { audio: { sampleCount }, video: { frameCount } } = metadata
const videoEncoder = new VideoEncoder({
output: (chunk, meta) => {
encodeFrame(chunk, meta);
encodedFrameCount += 1;
if (encodedFrameCount % 100 === 0) {
console.log('encoded', encodedFrameCount, 'frames out of ', frameCount);
}
callback(encodedFrameCount / frameCount)
if (encodedFrameCount >= frameCount) {
videoEncoder.flush().then(() => {
console.log('flushed video')
if (encodedSampleCount >= sampleCount) {
muxerFinalize()
resolve()
}
})
}
},
error: (error) => {
console.error('error', error);
reject(error)
},
});
const audioEncoder = new AudioEncoder({
output: (chunk, meta) => {
encodeAudio(chunk, meta);
encodedSampleCount += 1;
if (encodedSampleCount % 100 === 0) {
console.log('encoded', encodedSampleCount, 'samples out of ', sampleCount);
}
if (encodedSampleCount + 1 >= sampleCount) {
audioEncoder.flush().then(() => {
console.log('flushed audio')
if (encodedFrameCount >= frameCount) {
muxerFinalize()
resolve()
}
})
}
},
error: (error) => {
console.error('error', error)
reject(error)
}
});
const audioConfig: AudioEncoderConfig = {
...metadata.audio,
bitrate: 128000, // Default to 128kbps
}
for (const validBitrates of [96000, 128000, 160000, 192000]) {
if (validBitrates >= metadata.audio.bitrate) {
audioConfig.bitrate = validBitrates;
break;
}
}
videoEncoder.configure(videoConfig);
audioEncoder.configure(audioConfig);
return {
videoEncoder,
audioEncoder,
close: () => close
};
Do you mean when I've sent all the
decodes
?Something like this:
Yes, exactly! That's all you need to do, you do not need to flush the encoder inside the callback.
When you have two encoders, it's just:
await videoEncoder.flush();
await audioEncoder.flush();
muxer.finalize();
Ok, will try and get back to you, huge thanks 💜
Ah I closed, but anyway I don't think the issue comes from this lib :)
Alright, let me know if you encounter anything else.
Hey, I'm getting a stack trace error, kinda randomly here
Do you know where it could come from?
Thanks for the work, it's greatly appreciated!