Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Latest commit

 

History

History
History
192 lines (161 loc) · 6.8 KB

File metadata and controls

192 lines (161 loc) · 6.8 KB
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
* High-Level API Example: DASH Streaming
*
* Shows how to create MPEG-DASH streams from RTSP/file sources.
* Demonstrates fragmented MP4 output with configurable segment duration.
*
* Usage: tsx examples/api-dash.ts <input> <output-dir> [options]
*
* Options:
* --duration <n> Recording duration in seconds (default: 10)
* --segment <n> DASH segment duration in seconds (default: 2)
* --window-size <n> DASH window size (default: 10)
* --bitrate <rate> Video bitrate (default: 5M)
* --preset <preset> Encoder preset (default: ultrafast)
*
* Examples:
* tsx examples/api-dash.ts testdata/video.mp4 examples/.tmp/dash --bitrate 2M --segment 4
* tsx examples/api-dash.ts rtsp://camera.local/stream examples/.tmp/dash
* tsx examples/api-dash.ts rtsp://admin:pass@192.168.1.100/ch1 examples/.tmp/dash --duration 30
* tsx examples/api-dash.ts rtsp://server/live examples/.tmp/dash --preset medium --window-size 20
*/
import fs from 'fs/promises';
import { AV_PIX_FMT_YUV420P, Decoder, Demuxer, Encoder, FF_ENCODER_LIBX264, FilterAPI, FilterPreset, Muxer } from '../src/index.js';
import { prepareTestEnvironment } from './index.js';
// Parse command line arguments
const args = process.argv.slice(2);
const inputUrl = args[0];
const outputDir = args[1];
if (!inputUrl || !outputDir || inputUrl.startsWith('--') || outputDir.startsWith('--')) {
console.error('Usage: tsx examples/api-dash.ts <input> <output-dir> [options]');
console.error('Options:');
console.error(' --duration <n> Recording duration in seconds (default: 10)');
console.error(' --segment <n> DASH segment duration in seconds (default: 2)');
console.error(' --window-size <n> DASH window size (default: 10)');
console.error(' --bitrate <rate> Video bitrate (default: 5M)');
console.error(' --preset <preset> Encoder preset (default: ultrafast)');
process.exit(1);
}
// Parse options
const durationIndex = args.indexOf('--duration');
const duration = durationIndex !== -1 ? parseInt(args[durationIndex + 1]) : 10;
const segmentIndex = args.indexOf('--segment');
const segmentDuration = segmentIndex !== -1 ? parseInt(args[segmentIndex + 1]) : 2;
const windowSizeIndex = args.indexOf('--window-size');
const windowSize = windowSizeIndex !== -1 ? parseInt(args[windowSizeIndex + 1]) : 10;
const bitrateIndex = args.indexOf('--bitrate');
const bitrate = bitrateIndex !== -1 ? args[bitrateIndex + 1] : '5M';
const presetIndex = args.indexOf('--preset');
const preset = presetIndex !== -1 ? args[presetIndex + 1] : 'ultrafast';
let stop = false;
prepareTestEnvironment();
console.log(`Input: ${inputUrl}`);
console.log(`Output Directory: ${outputDir}`);
console.log(`Duration: ${duration} seconds`);
console.log(`Segment Duration: ${segmentDuration} seconds`);
console.log(`Window Size: ${windowSize}`);
console.log(`Bitrate: ${bitrate}`);
console.log(`Encoder Preset: ${preset}`);
// Prepare output directory
const dashManifestPath = `${outputDir}/manifest.mpd`;
console.log('Preparing output directory...');
await fs.rm(outputDir, { recursive: true, force: true });
await fs.mkdir(outputDir, { recursive: true });
// Detect if input is RTSP
const isRtsp = inputUrl.toLowerCase().startsWith('rtsp://');
// Open input
console.log(isRtsp ? 'Connecting to RTSP stream...' : 'Opening input file...');
await using input = await Demuxer.open(inputUrl, {
options: isRtsp ? { rtsp_transport: 'tcp' } : undefined,
});
// Get video stream
const videoStream = input.video();
if (!videoStream) {
throw new Error('No video stream found in input');
}
// Display input information
console.log('\nInput Information:');
console.log(`Video: ${videoStream.codecpar.width}x${videoStream.codecpar.height}`);
console.log(`Codec: ${videoStream.codecpar.codecId}`);
console.log(`Format: ${videoStream.codecpar.format}`);
console.log(`Time base: ${videoStream.timeBase.num}/${videoStream.timeBase.den}`);
console.log(`Frame rate: ${videoStream.avgFrameRate.num}/${videoStream.avgFrameRate.den}`);
// Create decoder
console.log('\nCreating video decoder...');
using decoder = await Decoder.create(videoStream);
// Create filter (ensure YUV420P for DASH compatibility)
const filterChain = FilterPreset.chain().format(AV_PIX_FMT_YUV420P).build();
console.log(`\nCreating filter: ${filterChain}`);
using filter = FilterAPI.create(filterChain, {
framerate: videoStream.avgFrameRate,
});
// Create encoder
console.log('\nCreating H.265 encoder...');
using encoder = await Encoder.create(FF_ENCODER_LIBX264, {
decoder,
filter,
bitrate,
options: {
preset,
tune: isRtsp ? 'zerolatency' : undefined,
},
});
// Create DASH output
console.log('\nCreating DASH output...');
await using dashOutput = await Muxer.open(dashManifestPath, {
input, // Pass input for automatic metadata copying (Title, etc.)
options: {
movflags: 'frag_keyframe+empty_moov+default_base_moof',
seg_duration: segmentDuration,
window_size: windowSize,
extra_window_size: Math.floor(windowSize / 2),
},
});
// Add video stream
const dashVideoStreamIndex = dashOutput.addStream(videoStream, { encoder });
console.log('\nDASH Configuration:');
console.log(`Segment Duration: ${segmentDuration}s`);
console.log(`Window Size: ${windowSize} segments`);
console.log(`Manifest: ${dashManifestPath}`);
// Set up timeout for recording duration
const timeout = setTimeout(() => {
console.log(`\nRecording duration reached (${duration}s), stopping...`);
stop = true;
}, duration * 1000);
// Process streams
console.log('\nStreaming started...');
const startTime = Date.now();
let packetsWritten = 0;
let framesProcessed = 0;
try {
// Create processing pipeline
const videoInputGenerator = input.packets(videoStream.index);
const videoDecoderGenerator = decoder.frames(videoInputGenerator);
const videoFilterGenerator = filter.frames(videoDecoderGenerator);
const videoEncoderGenerator = encoder.packets(videoFilterGenerator);
// Process video
for await (const packet of videoEncoderGenerator) {
if (stop) {
break;
}
await dashOutput.writePacket(packet, dashVideoStreamIndex);
packetsWritten++;
framesProcessed++;
// Progress indicator
if (packetsWritten % 30 === 0) {
const elapsed = (Date.now() - startTime) / 1000;
const fps = framesProcessed / elapsed;
console.log(`Streaming: ${elapsed.toFixed(1)}s - Frames: ${framesProcessed} (${fps.toFixed(1)} fps)`);
}
}
} finally {
clearTimeout(timeout);
}
const elapsed = (Date.now() - startTime) / 1000;
console.log('\nStreaming complete!');
console.log(`Duration: ${elapsed.toFixed(2)} seconds`);
console.log(`Frames processed: ${framesProcessed}`);
console.log(`Average FPS: ${(framesProcessed / elapsed).toFixed(2)}`);
console.log(`Packets written: ${packetsWritten}`);
console.log(`DASH manifest: ${dashManifestPath}`);
console.log(`\nTo play: ffplay ${dashManifestPath}`);
Morty Proxy This is a proxified and sanitized view of the page, visit original site.