Compare commits

...
11 Commits
17 changed files with 170 additions and 99 deletions
+6 -3
View File
@@ -24,7 +24,7 @@
chunked: true,
chunkSize: 2**20
});
const outputFormat = new Mediabunny.Mp4OutputFormat({});
const outputFormat = new Mediabunny.WavOutputFormat({});
const button = document.createElement('button');
button.textContent = 'Cancel';
@@ -72,6 +72,9 @@
}),
output,
audio: {
codec: 'pcm-s16',
//sampleRate: 16000,
//numberOfChannels: 1,
//discard: true,
//codec: 'opus',
//bitrate: 128000,
@@ -106,7 +109,7 @@
*/
video: () => ({
//discard: true,
forceTranscode: true,
//forceTranscode: true,
//codec: 'avc',
//fit: 'contain',
//frameRate: 27.123,
@@ -129,7 +132,7 @@
//height: 100,
}),
trim: {
start: 1,
start: 0,
end: 10
},
});
+13 -3
View File
@@ -14,10 +14,20 @@
source: new Mediabunny.BlobSource(file),
});
const videoTrack = await input.getPrimaryVideoTrack();
const sink = new Mediabunny.VideoSampleSink(videoTrack);
const audioTrack = await input.getPrimaryAudioTrack();
const sink = new Mediabunny.EncodedPacketSink(audioTrack);
console.log(await sink.getSample(await videoTrack.getFirstTimestamp()))
for await (const packet of sink.packets()) {
console.log(packet);
}
/*
const sink = new Mediabunny.EncodedPacketSink(videoTrack);
for await (const packet of sink.packets()) {
console.log(packet)
}
*/
/*
for await (const sample of sink.samples(0.99)) {
+6 -6
View File
@@ -60,6 +60,12 @@ await input.computeDuration(); // => 1905.4615
```
More specifically, the duration is defined as the maximum end timestamp across all tracks.
Mediabunny also lets you read descriptive metadata tags from media files, such as title, artist, or cover art:
```ts
await input.getMetadataTags(); // => MetadataTags
```
For more info, see [`MetadataTags`](../api/MetadataTags).
## Reading track metadata
You can extract the list of all media tracks in the file like so:
@@ -76,12 +82,6 @@ await input.getPrimaryVideoTrack(); // => InputVideoTrack | null
await input.getPrimaryAudioTrack(); // => InputAudioTrack | null
```
Mediabunny also lets you read descriptive metadata tags from media files, such as title, artist, or cover art:
```ts
await input.getMetadataTags(); // => MetadataTags
```
For more info, see [`MetadataTags`](../api/MetadataTags).
::: info
Subtitle tracks are currently not supported for reading.
:::
+1
View File
@@ -105,6 +105,7 @@ const sponsors = {
{ image: 'https://avatars.githubusercontent.com/u/9549394', name: 'studnitz', url: 'https://github.com/studnitz' },
{ image: 'https://avatars.githubusercontent.com/u/504909', name: 'Hirbod', url: 'https://github.com/hirbod' },
{ image: 'https://avatars.githubusercontent.com/u/30229596', name: 'Pablo Bonilla', url: 'https://github.com/devPablo' },
{ image: 'https://avatars.githubusercontent.com/u/38181164', name: 'wcw', url: 'https://github.com/asd55667' },
{ image: 'https://avatars.githubusercontent.com/u/63088713', name: 'taf2000', url: 'https://github.com/taf2000' },
{ image: 'https://avatars.githubusercontent.com/u/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' },
{ image: 'https://avatars.githubusercontent.com/u/91711202', name: 'ihasq', url: 'https://github.com/ihasq' },
@@ -96,6 +96,13 @@ const generateThumbnails = async (resource: File | string) => {
timestampElement.className
= 'absolute bottom-0 right-0 bg-black/30 text-white px-1 py-0.5 text-[11px] rounded-tl-lg';
container.append(timestampElement);
} else {
// Add something to indicate that the thumbnail is missing
const p = document.createElement('p');
p.textContent = '?';
p.className = 'absolute inset-0 flex items-center justify-center text-3xl opacity-50';
container.append(p);
}
i++;
+6 -6
View File
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
"version": "1.14.2",
"version": "1.14.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
"version": "1.14.2",
"version": "1.14.4",
"license": "MPL-2.0",
"workspaces": [
"packages/*"
@@ -7749,9 +7749,9 @@
}
},
"node_modules/mediabunny": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.14.1.tgz",
"integrity": "sha512-TjLg8GQGGsnGePcA6i0NItGe9a5G8WaleCGaXpb5V5okwuK5KpNKfcmTZXItnjfPLo7FvfEZI0NFp1lIR8Os7Q==",
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.14.3.tgz",
"integrity": "sha512-kCvieRo6X1QDcdWLjn7o2BY/VCDeyU9nNGBVjOIOiWPoTtIekHR+viKAYaZafLEm0poBv+O2PwttL37PaSo/kA==",
"license": "MPL-2.0",
"peer": true,
"workspaces": [
@@ -12242,7 +12242,7 @@
},
"packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder",
"version": "1.14.2",
"version": "1.14.4",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
"version": "1.14.2",
"version": "1.14.4",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"workspaces": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/mp3-encoder",
"author": "Vanilagy",
"version": "1.14.2",
"version": "1.14.4",
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
+67 -50
View File
@@ -26,8 +26,27 @@ import { EncodedPacket, PacketType } from './packet';
// Rec. ITU-T H.265
// https://stackoverflow.com/questions/24884827
export enum AvcNalUnitType {
IDR = 5,
SPS = 7,
PPS = 8,
SPS_EXT = 13,
}
export enum HevcNalUnitType {
RASL_N = 8,
RASL_R = 9,
BLA_W_LP = 16,
RSV_IRAP_VCL23 = 23,
VPS_NUT = 32,
SPS_NUT = 33,
PPS_NUT = 34,
PREFIX_SEI_NUT = 39,
SUFFIX_SEI_NUT = 40,
}
/** Finds all NAL units in an AVC packet in Annex B format. */
const findNalUnitsInAnnexB = (packetData: Uint8Array) => {
export const findNalUnitsInAnnexB = (packetData: Uint8Array) => {
const nalUnits: Uint8Array[] = [];
let i = 0;
@@ -184,6 +203,21 @@ export type AvcDecoderConfigurationRecord = {
sequenceParameterSetExt: Uint8Array[] | null;
};
export const extractAvcNalUnits = (packetData: Uint8Array, decoderConfig: VideoDecoderConfig) => {
if (decoderConfig.description) {
// Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
const bytes = toUint8Array(decoderConfig.description);
const lengthSizeMinusOne = bytes[4]! & 0b11;
const lengthSize = (lengthSizeMinusOne + 1) as 1 | 2 | 3 | 4;
return findNalUnitsInLengthPrefixed(packetData, lengthSize);
} else {
// Stream is in Annex B format
return findNalUnitsInAnnexB(packetData);
}
};
const extractNalUnitTypeForAvc = (data: Uint8Array) => {
return data[0]! & 0x1F;
};
@@ -193,9 +227,9 @@ export const extractAvcDecoderConfigurationRecord = (packetData: Uint8Array) =>
try {
const nalUnits = findNalUnitsInAnnexB(packetData);
const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === 7);
const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === 8);
const spsExtUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === 13);
const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.SPS);
const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.PPS);
const spsExtUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.SPS_EXT);
if (spsUnits.length === 0) {
return null;
@@ -337,12 +371,6 @@ export const serializeAvcDecoderConfigurationRecord = (record: AvcDecoderConfigu
return new Uint8Array(bytes);
};
const NALU_TYPE_VPS = 32;
const NALU_TYPE_SPS = 33;
const NALU_TYPE_PPS = 34;
const NALU_TYPE_SEI_PREFIX = 39;
const NALU_TYPE_SEI_SUFFIX = 40;
// Data specified in ISO 14496-15
export type HevcDecoderConfigurationRecord = {
configurationVersion: number;
@@ -369,7 +397,22 @@ export type HevcDecoderConfigurationRecord = {
}[];
};
const extractNalUnitTypeForHevc = (data: Uint8Array) => {
export const extractHevcNalUnits = (packetData: Uint8Array, decoderConfig: VideoDecoderConfig) => {
if (decoderConfig.description) {
// Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
const bytes = toUint8Array(decoderConfig.description);
const lengthSizeMinusOne = bytes[21]! & 0b11;
const lengthSize = (lengthSizeMinusOne + 1) as 1 | 2 | 3 | 4;
return findNalUnitsInLengthPrefixed(packetData, lengthSize);
} else {
// Stream is in Annex B format
return findNalUnitsInAnnexB(packetData);
}
};
export const extractNalUnitTypeForHevc = (data: Uint8Array) => {
return (data[0]! >> 1) & 0x3F;
};
@@ -380,12 +423,12 @@ export const extractHevcDecoderConfigurationRecord = (
try {
const nalUnits = findNalUnitsInAnnexB(packetData);
const vpsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === NALU_TYPE_VPS);
const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === NALU_TYPE_SPS);
const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === NALU_TYPE_PPS);
const vpsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.VPS_NUT);
const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.SPS_NUT);
const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.PPS_NUT);
const seiUnits = nalUnits.filter(
unit => extractNalUnitTypeForHevc(unit) === NALU_TYPE_SEI_PREFIX
|| extractNalUnitTypeForHevc(unit) === NALU_TYPE_SEI_SUFFIX,
unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.PREFIX_SEI_NUT
|| extractNalUnitTypeForHevc(unit) === HevcNalUnitType.SUFFIX_SEI_NUT,
);
if (spsUnits.length === 0 || ppsUnits.length === 0) return null;
@@ -521,7 +564,7 @@ export const extractHevcDecoderConfigurationRecord = (
? [
{
arrayCompleteness: 1,
nalUnitType: NALU_TYPE_VPS,
nalUnitType: HevcNalUnitType.VPS_NUT,
nalUnits: vpsUnits,
},
]
@@ -530,7 +573,7 @@ export const extractHevcDecoderConfigurationRecord = (
? [
{
arrayCompleteness: 1,
nalUnitType: NALU_TYPE_SPS,
nalUnitType: HevcNalUnitType.SPS_NUT,
nalUnits: spsUnits,
},
]
@@ -539,7 +582,7 @@ export const extractHevcDecoderConfigurationRecord = (
? [
{
arrayCompleteness: 1,
nalUnitType: NALU_TYPE_PPS,
nalUnitType: HevcNalUnitType.PPS_NUT,
nalUnits: ppsUnits,
},
]
@@ -1440,22 +1483,9 @@ export const determineVideoPacketType = async (
const decoderConfig = await videoTrack.getDecoderConfig();
assert(decoderConfig);
let nalUnits: Uint8Array[];
const nalUnits = extractAvcNalUnits(packet.data, decoderConfig);
const isKeyframe = nalUnits.some(x => extractNalUnitTypeForAvc(x) === AvcNalUnitType.IDR);
if (decoderConfig.description) {
// Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
const bytes = toUint8Array(decoderConfig.description);
const lengthSizeMinusOne = bytes[4]! & 0b11;
const lengthSize = (lengthSizeMinusOne + 1) as 1 | 2 | 3 | 4;
nalUnits = findNalUnitsInLengthPrefixed(packet.data, lengthSize);
} else {
// Stream is in Annex B format
nalUnits = findNalUnitsInAnnexB(packet.data);
}
const isKeyframe = nalUnits.some(x => extractNalUnitTypeForAvc(x) === 5);
return isKeyframe ? 'key' : 'delta';
};
@@ -1463,25 +1493,12 @@ export const determineVideoPacketType = async (
const decoderConfig = await videoTrack.getDecoderConfig();
assert(decoderConfig);
let nalUnits: Uint8Array[];
if (decoderConfig.description) {
// Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
const bytes = toUint8Array(decoderConfig.description);
const lengthSizeMinusOne = bytes[21]! & 0b11;
const lengthSize = (lengthSizeMinusOne + 1) as 1 | 2 | 3 | 4;
nalUnits = findNalUnitsInLengthPrefixed(packet.data, lengthSize);
} else {
// Stream is in Annex B format
nalUnits = findNalUnitsInAnnexB(packet.data);
}
const nalUnits = extractHevcNalUnits(packet.data, decoderConfig);
const isKeyframe = nalUnits.some((x) => {
const type = extractNalUnitTypeForHevc(x);
return 16 <= type && type <= 23;
return HevcNalUnitType.BLA_W_LP <= type && type <= HevcNalUnitType.RSV_IRAP_VCL23;
});
return isKeyframe ? 'key' : 'delta';
};
+1 -1
View File
@@ -621,7 +621,7 @@ export const parseAacAudioSpecificConfig = (bytes: Uint8Array | null): AacAudioS
};
};
export const OPUS_INTERNAL_SAMPLE_RATE = 48000;
export const OPUS_SAMPLE_RATE = 48_000;
const PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
+17 -17
View File
@@ -1125,8 +1125,6 @@ export class Conversion {
await this._started;
const resampler = new AudioResampler({
sourceNumberOfChannels: track.numberOfChannels,
sourceSampleRate: track.sampleRate,
targetNumberOfChannels,
targetSampleRate,
startTime: this._startTimestamp,
@@ -1246,9 +1244,9 @@ class TrackSynchronizer {
* OfflineAudioContext.
*/
export class AudioResampler {
sourceSampleRate: number;
sourceSampleRate: number | null = null;
targetSampleRate: number;
sourceNumberOfChannels: number;
sourceNumberOfChannels: number | null = null;
targetNumberOfChannels: number;
startTime: number;
endTime: number;
@@ -1262,20 +1260,16 @@ export class AudioResampler {
/** The highest index written to in the current buffer */
maxWrittenFrame: number;
channelMixer!: (sourceData: Float32Array, sourceFrameIndex: number, targetChannelIndex: number) => number;
tempSourceBuffer: Float32Array;
tempSourceBuffer!: Float32Array;
constructor(options: {
sourceSampleRate: number;
targetSampleRate: number;
sourceNumberOfChannels: number;
targetNumberOfChannels: number;
startTime: number;
endTime: number;
onSample: (sample: AudioSample) => Promise<void>;
}) {
this.sourceSampleRate = options.sourceSampleRate;
this.targetSampleRate = options.targetSampleRate;
this.sourceNumberOfChannels = options.sourceNumberOfChannels;
this.targetNumberOfChannels = options.targetNumberOfChannels;
this.startTime = options.startTime;
this.endTime = options.endTime;
@@ -1287,17 +1281,14 @@ export class AudioResampler {
this.outputBuffer = new Float32Array(this.bufferSizeInSamples);
this.bufferStartFrame = 0;
this.maxWrittenFrame = -1;
this.setupChannelMixer();
// Pre-allocate temporary buffer for source data
this.tempSourceBuffer = new Float32Array(this.sourceSampleRate * this.sourceNumberOfChannels);
}
/**
* Sets up the channel mixer to handle up/downmixing in the case where input and output channel counts don't match.
*/
setupChannelMixer(): void {
doChannelMixerSetup(): void {
assert(this.sourceNumberOfChannels !== null);
const sourceNum = this.sourceNumberOfChannels;
const targetNum = this.targetNumberOfChannels;
@@ -1415,8 +1406,17 @@ export class AudioResampler {
}
async add(audioSample: AudioSample) {
if (!audioSample || audioSample._closed) {
return;
if (this.sourceSampleRate === null) {
// This is the first sample, so let's init the missing data. Initting the sample rate from the decoded
// sample is more reliable than using the file's metadata, because decoders are free to emit any sample rate
// they see fit.
this.sourceSampleRate = audioSample.sampleRate;
this.sourceNumberOfChannels = audioSample.numberOfChannels;
// Pre-allocate temporary buffer for source data
this.tempSourceBuffer = new Float32Array(this.sourceSampleRate * this.sourceNumberOfChannels);
this.doChannelMixerSetup();
}
const requiredSamples = audioSample.numberOfFrames * audioSample.numberOfChannels;
+6 -1
View File
@@ -12,6 +12,7 @@ import {
extractAudioCodecString,
extractVideoCodecString,
MediaCodec,
OPUS_SAMPLE_RATE,
parseAacAudioSpecificConfig,
parsePcmCodec,
PCM_AUDIO_CODECS,
@@ -1049,6 +1050,10 @@ export class IsobmffDemuxer extends Demuxer {
}
}
if (track.info.codec === 'opus') {
sampleRate = OPUS_SAMPLE_RATE; // Always the same
}
track.info.numberOfChannels = channelCount;
track.info.sampleRate = sampleRate;
@@ -1421,7 +1426,7 @@ export class IsobmffDemuxer extends Demuxer {
track.info.codecDescription = description;
track.info.numberOfChannels = outputChannelCount;
track.info.sampleRate = inputSampleRate;
// Don't copy the input sample rate, irrelevant, and output sample rate is fixed
}; break;
case 'dfLa': { // Used for FLAC audio
+2
View File
@@ -18,6 +18,7 @@ import {
extractAudioCodecString,
extractVideoCodecString,
MediaCodec,
OPUS_SAMPLE_RATE,
VideoCodec,
} from '../codec';
import { Demuxer } from '../demuxer';
@@ -988,6 +989,7 @@ export class MatroskaDemuxer extends Demuxer {
} else if (codecIdWithoutSuffix === CODEC_STRING_MAP.opus) {
this.currentTrack.info.codec = 'opus';
this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
this.currentTrack.info.sampleRate = OPUS_SAMPLE_RATE; // Always the same
} else if (codecIdWithoutSuffix === CODEC_STRING_MAP.vorbis) {
this.currentTrack.info.codec = 'vorbis';
this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
+2 -2
View File
@@ -47,7 +47,7 @@ import {
parseSubtitleTimestamp,
} from '../subtitles';
import {
OPUS_INTERNAL_SAMPLE_RATE,
OPUS_SAMPLE_RATE,
PCM_AUDIO_CODECS,
PcmAudioCodec,
SubtitleCodec,
@@ -293,7 +293,7 @@ export class MatroskaMuxer extends Muxer {
const header = parseOpusIdentificationHeader(bytes);
// Use the preSkip value from the header
seekPreRollNs = Math.round(1e9 * (header.preSkip / OPUS_INTERNAL_SAMPLE_RATE));
seekPreRollNs = Math.round(1e9 * (header.preSkip / OPUS_SAMPLE_RATE));
}
}
+29 -3
View File
@@ -7,6 +7,7 @@
*/
import { parsePcmCodec, PCM_AUDIO_CODECS, PcmAudioCodec, VideoCodec, AudioCodec } from './codec';
import { extractHevcNalUnits, extractNalUnitTypeForHevc, HevcNalUnitType } from './codec-data';
import { CustomVideoDecoder, customVideoDecoders, CustomAudioDecoder, customAudioDecoders } from './custom-coder';
import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track';
import {
@@ -624,8 +625,8 @@ export abstract class BaseMediaSampleSink<
const nextPacket = await packetSink.getNextPacket(currentPacket);
assert(nextPacket);
currentPacket = nextPacket;
decoder.decode(nextPacket);
currentPacket = nextPacket;
}
maxSequenceNumber = -1;
@@ -757,12 +758,14 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
inputTimestamps: number[] = []; // Timestamps input into the decoder, sorted.
sampleQueue: VideoSample[] = []; // Safari-specific thing, check usage.
currentPacketIndex = 0;
raslSkipped = false; // For HEVC stuff
constructor(
onSample: (sample: VideoSample) => unknown,
onError: (error: DOMException) => unknown,
codec: VideoCodec,
decoderConfig: VideoDecoderConfig,
public codec: VideoCodec,
public decoderConfig: VideoDecoderConfig,
public rotation: Rotation,
public timeResolution: number,
) {
@@ -848,6 +851,26 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
}
decode(packet: EncodedPacket) {
if (this.codec === 'hevc' && this.currentPacketIndex > 0 && !this.raslSkipped) {
// If we're using HEVC, we need to make sure to skip any RASL slices that follow a non-IDR key frame such as
// CRA_NUT. This is because RASL slices cannot be decoded without data before the CRA_NUT. Browsers behave
// differently here: Chromium drops the packets, Safari throws a decoder error. Either way, it's not good
// and causes bugs upstream. So, let's take the dropping into our own hands.
const nalUnits = extractHevcNalUnits(packet.data, this.decoderConfig);
const hasRaslPicture = nalUnits.some((x) => {
const type = extractNalUnitTypeForHevc(x);
return type === HevcNalUnitType.RASL_N || type === HevcNalUnitType.RASL_R;
});
if (hasRaslPicture) {
return; // Drop
}
this.raslSkipped = true;
}
this.currentPacketIndex++;
if (this.customDecoder) {
this.customDecoderQueueSize++;
void this.customDecoderCallSerializer
@@ -879,6 +902,9 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
this.sampleQueue.length = 0;
}
this.currentPacketIndex = 0;
this.raslSkipped = false;
}
close() {
+3 -3
View File
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { OPUS_INTERNAL_SAMPLE_RATE } from '../codec';
import { OPUS_SAMPLE_RATE } from '../codec';
import { parseModesFromVorbisSetupPacket, parseOpusIdentificationHeader } from '../codec-data';
import { Demuxer } from '../demuxer';
import { Input } from '../input';
@@ -249,7 +249,7 @@ export class OggDemuxer extends Demuxer {
const header = parseOpusIdentificationHeader(firstPacket.data);
bitstream.numberOfChannels = header.outputChannelCount;
bitstream.sampleRate = header.inputSampleRate;
bitstream.sampleRate = OPUS_SAMPLE_RATE; // Always the same
bitstream.codecInfo.opusInfo = {
preSkip: header.preSkip,
@@ -574,7 +574,7 @@ class OggAudioTrackBacking implements InputAudioTrackBacking {
constructor(public bitstream: LogicalBitstream, public demuxer: OggDemuxer) {
// Opus always uses a fixed sample rate for its internal calculations, even if the actual rate is different
this.internalSampleRate = bitstream.codecInfo.codec === 'opus'
? OPUS_INTERNAL_SAMPLE_RATE
? OPUS_SAMPLE_RATE
: bitstream.sampleRate;
}
+2 -2
View File
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { OPUS_INTERNAL_SAMPLE_RATE, validateAudioChunkMetadata } from '../codec';
import { OPUS_SAMPLE_RATE, validateAudioChunkMetadata } from '../codec';
import { parseModesFromVorbisSetupPacket, parseOpusIdentificationHeader } from '../codec-data';
import {
assert,
@@ -119,7 +119,7 @@ export class OggMuxer extends Muxer {
track,
serialNumber,
internalSampleRate: track.source._codec === 'opus'
? OPUS_INTERNAL_SAMPLE_RATE
? OPUS_SAMPLE_RATE
: meta.decoderConfig.sampleRate,
codecInfo: {
codec: track.source._codec,