166 lines
8.2 KiB
JavaScript
166 lines
8.2 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.reencodeAudioTrack = void 0;
|
|
const media_parser_1 = require("@remotion/media-parser");
|
|
const audio_decoder_config_1 = require("./audio-decoder-config");
|
|
const audio_encoder_1 = require("./audio-encoder");
|
|
const audio_encoder_config_1 = require("./audio-encoder-config");
|
|
const convert_encoded_chunk_1 = require("./convert-encoded-chunk");
|
|
const create_audio_decoder_1 = require("./create-audio-decoder");
|
|
const log_1 = require("./log");
|
|
const processing_queue_1 = require("./processing-queue");
|
|
const reencodeAudioTrack = async ({ audioOperation, track, logLevel, abortConversion, state, controller, onMediaStateUpdate, onAudioData, progressTracker, }) => {
|
|
if (audioOperation.type !== 'reencode') {
|
|
throw new Error(`Audio track with ID ${track.trackId} could not be resolved with a valid operation. Received ${JSON.stringify(audioOperation)}, but must be either "copy", "reencode", "drop" or "fail"`);
|
|
}
|
|
const audioEncoderConfig = await (0, audio_encoder_config_1.getAudioEncoderConfig)({
|
|
numberOfChannels: track.numberOfChannels,
|
|
sampleRate: audioOperation.sampleRate ?? track.sampleRate,
|
|
codec: audioOperation.audioCodec,
|
|
bitrate: audioOperation.bitrate,
|
|
});
|
|
const audioDecoderConfig = await (0, audio_decoder_config_1.getAudioDecoderConfig)({
|
|
codec: track.codec,
|
|
numberOfChannels: track.numberOfChannels,
|
|
sampleRate: track.sampleRate,
|
|
description: track.description,
|
|
});
|
|
log_1.Log.verbose(logLevel, 'Audio encoder config', audioEncoderConfig);
|
|
log_1.Log.verbose(logLevel, 'Audio decoder config', audioDecoderConfig ?? track);
|
|
if (!audioEncoderConfig) {
|
|
abortConversion(new Error(`Could not configure audio encoder of track ${track.trackId}`));
|
|
return null;
|
|
}
|
|
if (!audioDecoderConfig) {
|
|
abortConversion(new Error(`Could not configure audio decoder of track ${track.trackId}`));
|
|
return null;
|
|
}
|
|
const codecPrivate = audioOperation.audioCodec === 'aac'
|
|
? media_parser_1.MediaParserInternals.createAacCodecPrivate({
|
|
audioObjectType: 2,
|
|
sampleRate: audioOperation.sampleRate ?? audioEncoderConfig.sampleRate,
|
|
channelConfiguration: audioEncoderConfig.numberOfChannels,
|
|
codecPrivate: null,
|
|
})
|
|
: null;
|
|
const { trackNumber } = await state.addTrack({
|
|
type: 'audio',
|
|
codec: audioOperation.audioCodec === 'wav'
|
|
? 'pcm-s16'
|
|
: audioOperation.audioCodec,
|
|
numberOfChannels: audioEncoderConfig.numberOfChannels,
|
|
sampleRate: audioOperation.sampleRate ?? audioEncoderConfig.sampleRate,
|
|
codecPrivate,
|
|
timescale: track.originalTimescale,
|
|
});
|
|
const audioEncoder = (0, audio_encoder_1.createAudioEncoder)({
|
|
// This is weird 😵💫
|
|
// Chrome completely ignores the sample rate and uses it's own
|
|
// We cannot determine it here because it depends on the system
|
|
// sample rate. Unhardcode then declare it later once we know.
|
|
onNewAudioSampleRate: (sampleRate) => {
|
|
state.updateTrackSampleRate({ sampleRate, trackNumber });
|
|
},
|
|
onChunk: async (chunk) => {
|
|
await state.addSample({
|
|
chunk: (0, convert_encoded_chunk_1.convertEncodedChunk)(chunk),
|
|
trackNumber,
|
|
isVideo: false,
|
|
codecPrivate,
|
|
});
|
|
onMediaStateUpdate?.((prevState) => {
|
|
return {
|
|
...prevState,
|
|
encodedAudioFrames: prevState.encodedAudioFrames + 1,
|
|
};
|
|
});
|
|
},
|
|
onError: (err) => {
|
|
abortConversion(new Error(`Audio encoder of track ${track.trackId} failed (see .cause of this error)`, {
|
|
cause: err,
|
|
}));
|
|
},
|
|
codec: audioOperation.audioCodec,
|
|
controller,
|
|
config: audioEncoderConfig,
|
|
logLevel,
|
|
});
|
|
const audioProcessingQueue = (0, processing_queue_1.processingQueue)({
|
|
controller,
|
|
label: 'AudioData processing queue',
|
|
logLevel,
|
|
onError(error) {
|
|
abortConversion(new Error(`Audio decoder of track ${track.trackId} failed. Config: ${JSON.stringify(audioDecoderConfig)} (see .cause of this error)`, {
|
|
cause: error,
|
|
}));
|
|
},
|
|
onOutput: async (audioData) => {
|
|
const newAudioData = onAudioData
|
|
? await onAudioData?.({ audioData, track })
|
|
: audioData;
|
|
if (newAudioData !== audioData) {
|
|
if (newAudioData.duration !== audioData.duration) {
|
|
throw new Error(`onAudioData returned a different duration than the input audio data. Original duration: ${audioData.duration}, new duration: ${newAudioData.duration}`);
|
|
}
|
|
if (newAudioData.numberOfChannels !== audioData.numberOfChannels) {
|
|
throw new Error(`onAudioData returned a different number of channels than the input audio data. Original channels: ${audioData.numberOfChannels}, new channels: ${newAudioData.numberOfChannels}`);
|
|
}
|
|
if (newAudioData.sampleRate !== audioData.sampleRate) {
|
|
throw new Error(`onAudioData returned a different sample rate than the input audio data. Original sample rate: ${audioData.sampleRate}, new sample rate: ${newAudioData.sampleRate}`);
|
|
}
|
|
if (newAudioData.format !== audioData.format) {
|
|
throw new Error(`onAudioData returned a different format than the input audio data. Original format: ${audioData.format}, new format: ${newAudioData.format}`);
|
|
}
|
|
if (newAudioData.timestamp !== audioData.timestamp) {
|
|
throw new Error(`onAudioData returned a different timestamp than the input audio data. Original timestamp: ${audioData.timestamp}, new timestamp: ${newAudioData.timestamp}`);
|
|
}
|
|
audioData.close();
|
|
}
|
|
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
await audioEncoder.ioSynchronizer.waitForQueueSize(10);
|
|
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
audioEncoder.encode(newAudioData);
|
|
onMediaStateUpdate?.((prevState) => {
|
|
return {
|
|
...prevState,
|
|
decodedAudioFrames: prevState.decodedAudioFrames + 1,
|
|
};
|
|
});
|
|
newAudioData.close();
|
|
},
|
|
});
|
|
const audioDecoder = await (0, create_audio_decoder_1.internalCreateAudioDecoder)({
|
|
onFrame: async (audioData) => {
|
|
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
await audioProcessingQueue.ioSynchronizer.waitForQueueSize(10);
|
|
audioProcessingQueue.input(audioData);
|
|
},
|
|
onError(error) {
|
|
abortConversion(new Error(`Audio decoder of track ${track.trackId} failed. Config: ${JSON.stringify(audioDecoderConfig)} (see .cause of this error)`, {
|
|
cause: error,
|
|
}));
|
|
},
|
|
controller,
|
|
config: audioDecoderConfig,
|
|
logLevel,
|
|
});
|
|
state.addWaitForFinishPromise(async () => {
|
|
log_1.Log.verbose(logLevel, 'Waiting for audio decoder to finish');
|
|
await audioDecoder.flush();
|
|
log_1.Log.verbose(logLevel, 'Audio decoder finished');
|
|
audioDecoder.close();
|
|
await audioProcessingQueue.ioSynchronizer.waitForQueueSize(0);
|
|
log_1.Log.verbose(logLevel, 'Audio processing queue finished');
|
|
await audioEncoder.waitForFinish();
|
|
log_1.Log.verbose(logLevel, 'Audio encoder finished');
|
|
audioEncoder.close();
|
|
});
|
|
return async (audioSample) => {
|
|
progressTracker.setPossibleLowestTimestamp(Math.min(audioSample.timestamp, audioSample.decodingTimestamp ?? Infinity));
|
|
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
await audioDecoder.waitForQueueToBeLessThan(10);
|
|
audioDecoder.decode(audioSample);
|
|
};
|
|
};
|
|
exports.reencodeAudioTrack = reencodeAudioTrack;
|