Skip to content

Commit 67ede61

Browse files
committed
Refactor DTS prediction in Demuxer to use native implementation and fix use after free in demuxer thread
1 parent 17170b2 commit 67ede61

File tree

1 file changed

+22
-69
lines changed

1 file changed

+22
-69
lines changed

src/api/demuxer.ts

Lines changed: 22 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { resolve } from 'path';
55
import { RtpPacket } from 'werift';
66

77
import {
8-
AV_CODEC_PROP_FIELDS,
98
AV_NOPTS_VALUE,
109
AV_PIX_FMT_NONE,
1110
AV_ROUND_NEAR_INF,
@@ -29,7 +28,7 @@ import { InputFormat } from '../lib/input-format.js';
2928
import { IOContext } from '../lib/io-context.js';
3029
import { Packet } from '../lib/packet.js';
3130
import { Rational } from '../lib/rational.js';
32-
import { avGetPixFmtName, avGetSampleFmtName, avInvQ, avMulQ, avRescaleQ, avRescaleQRnd } from '../lib/utilities.js';
31+
import { avGetPixFmtName, avGetSampleFmtName, avRescaleQ, avRescaleQRnd, dtsPredict as nativeDtsPredict } from '../lib/utilities.js';
3332
import { DELTA_THRESHOLD, DTS_ERROR_THRESHOLD, IO_BUFFER_SIZE, MAX_INPUT_QUEUE_SIZE } from './constants.js';
3433
import { IOStream } from './io-stream.js';
3534
import { StreamingUtils } from './utilities/streaming.js';
@@ -1667,6 +1666,14 @@ export class Demuxer implements AsyncDisposable, Disposable {
16671666

16681667
// Read next packet
16691668
const ret = await this.formatContext.readFrame(packet);
1669+
1670+
// IMPORTANT: Check isClosed again after await - the demuxer may have been
1671+
// closed while we were waiting for readFrame(). If closed, the native
1672+
// AVStreams have been freed and accessing them would cause use-after-free!
1673+
if (this.isClosed) {
1674+
break;
1675+
}
1676+
16701677
if (ret < 0) {
16711678
// End of stream - notify all waiting consumers
16721679
this.demuxEof = true;
@@ -1876,74 +1883,20 @@ export class Demuxer implements AsyncDisposable, Disposable {
18761883
*/
18771884
private dtsPredict(packet: Packet, stream: Stream): void {
18781885
const state = this.getStreamState(packet.streamIndex);
1879-
const par = stream.codecpar;
1880-
1881-
// First timestamp seen
1882-
if (!state.sawFirstTs) {
1883-
// For video with avg_frame_rate, account for video_delay
1884-
const avgFrameRate = stream.avgFrameRate;
1885-
if (avgFrameRate && avgFrameRate.num > 0) {
1886-
const frameRateD = Number(avgFrameRate.num) / Number(avgFrameRate.den);
1887-
state.firstDts = state.dts = BigInt(Math.floor((-par.videoDelay * Number(AV_TIME_BASE)) / frameRateD));
1888-
} else {
1889-
state.firstDts = state.dts = 0n;
1890-
}
1891-
1892-
if (packet.pts !== AV_NOPTS_VALUE) {
1893-
const ptsDts = avRescaleQ(packet.pts, packet.timeBase, AV_TIME_BASE_Q);
1894-
state.firstDts += ptsDts;
1895-
state.dts += ptsDts;
1896-
}
1897-
state.sawFirstTs = true;
1898-
}
1899-
1900-
// Initialize next_dts if not set
1901-
if (state.nextDts === AV_NOPTS_VALUE) {
1902-
state.nextDts = state.dts;
1903-
}
19041886

1905-
// Update from packet DTS if available
1906-
if (packet.dts !== AV_NOPTS_VALUE) {
1907-
state.nextDts = state.dts = avRescaleQ(packet.dts, packet.timeBase, AV_TIME_BASE_Q);
1908-
}
1909-
1910-
state.dts = state.nextDts;
1911-
1912-
// Predict next DTS based on codec type
1913-
switch (par.codecType) {
1914-
case AVMEDIA_TYPE_AUDIO:
1915-
// Audio: duration from sample_rate or packet duration
1916-
if (par.sampleRate >= 1 && par.frameSize > 0) {
1917-
state.nextDts += (BigInt(AV_TIME_BASE) * BigInt(par.frameSize)) / BigInt(par.sampleRate);
1918-
} else {
1919-
state.nextDts += avRescaleQ(packet.duration, packet.timeBase, AV_TIME_BASE_Q);
1920-
}
1921-
break;
1922-
1923-
case AVMEDIA_TYPE_VIDEO: {
1924-
// Video: various methods depending on available metadata
1925-
// Note: FFmpeg has ist->framerate (forced with -r), but we don't support that option
1926-
if (packet.duration > 0n) {
1927-
// Use packet duration
1928-
state.nextDts += avRescaleQ(packet.duration, packet.timeBase, AV_TIME_BASE_Q);
1929-
} else if (par.frameRate && par.frameRate.num > 0) {
1930-
// Use codec framerate with field handling
1931-
const fieldRate = avMulQ(par.frameRate, { num: 2, den: 1 });
1932-
let fields = 2; // Default: 2 fields (progressive or standard interlaced)
1933-
1934-
// Check if codec has fields property and parser is available
1935-
const parser = stream.parser;
1936-
if (par.hasProperties(AV_CODEC_PROP_FIELDS) && parser) {
1937-
// Get repeat_pict from parser for accurate field count
1938-
fields = 1 + parser.repeatPict;
1939-
}
1940-
1941-
const invFieldRate = avInvQ(fieldRate);
1942-
state.nextDts += avRescaleQ(BigInt(fields), invFieldRate, AV_TIME_BASE_Q);
1943-
}
1944-
break;
1945-
}
1946-
}
1887+
// Call native implementation with native objects
1888+
const newState = nativeDtsPredict(packet, stream, {
1889+
sawFirstTs: state.sawFirstTs,
1890+
dts: state.dts,
1891+
nextDts: state.nextDts,
1892+
firstDts: state.firstDts,
1893+
});
1894+
1895+
// Update state with results
1896+
state.sawFirstTs = newState.sawFirstTs;
1897+
state.dts = newState.dts;
1898+
state.nextDts = newState.nextDts;
1899+
state.firstDts = newState.firstDts;
19471900
}
19481901

19491902
/**

0 commit comments

Comments
 (0)