-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathrtsp_test.c
421 lines (358 loc) · 14.5 KB
/
rtsp_test.c
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
/**
* RTSP to MP4 Recorder - Simplified Test
*
* A simplified version of the rtsp_recorder.c program that focuses on
* recording an RTSP stream to an MP4 file with audio.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/time.h>
#include <libavutil/opt.h>
// Default values
#define DEFAULT_DURATION 20 // Default recording duration in seconds
#define DEFAULT_OUTPUT "test_output.mp4"
#define DEFAULT_RTSP_URL "rtsp://thingino:[email protected]:554/ch0"
// Global variables for signal handling
static int stop_recording = 0;
// Signal handler for graceful termination
void handle_signal(int sig) {
printf("Received signal %d, stopping recording...\n", sig);
stop_recording = 1;
}
// Function to log FFmpeg errors
void log_error(int err, const char *message) {
char error_buf[AV_ERROR_MAX_STRING_SIZE] = {0};
av_strerror(ret, error_buf, AV_ERROR_MAX_STRING_SIZE);
fprintf(stderr, "%s: %s\n", message, error_buf);
}
int main(int argc, char *argv[]) {
int ret;
int duration = DEFAULT_DURATION;
const char *output_file = DEFAULT_OUTPUT;
const char *rtsp_url = DEFAULT_RTSP_URL;
// Parse command line arguments
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--duration")) {
if (i + 1 < argc) {
duration = atoi(argv[i + 1]);
if (duration <= 0) {
duration = DEFAULT_DURATION;
}
i++;
}
} else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) {
if (i + 1 < argc) {
output_file = argv[i + 1];
i++;
}
} else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--input")) {
if (i + 1 < argc) {
rtsp_url = argv[i + 1];
i++;
}
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
printf("Usage: %s [options]\n", argv[0]);
printf("Options:\n");
printf(" -i, --input URL RTSP URL to record (default: %s)\n", DEFAULT_RTSP_URL);
printf(" -o, --output FILE Output MP4 file (default: %s)\n", DEFAULT_OUTPUT);
printf(" -d, --duration SEC Recording duration in seconds (default: %d)\n", DEFAULT_DURATION);
printf(" -h, --help Show this help message\n");
return 0;
}
}
printf("Recording from %s\n", rtsp_url);
printf("Output file: %s\n", output_file);
printf("Duration: %d seconds\n", duration);
// Register signal handlers for graceful termination
signal(SIGINT, handle_signal);
signal(SIGTERM, handle_signal);
// Initialize FFmpeg network
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
avformat_network_init();
// Input context
AVFormatContext *input_ctx = NULL;
// Set up RTSP options for low latency
AVDictionary *opts = NULL;
av_dict_set(&opts, "rtsp_transport", "tcp", 0); // Use TCP for RTSP (more reliable than UDP)
av_dict_set(&opts, "fflags", "nobuffer", 0); // Reduce buffering
av_dict_set(&opts, "flags", "low_delay", 0); // Low delay mode
av_dict_set(&opts, "max_delay", "500000", 0); // Maximum delay of 500ms
av_dict_set(&opts, "stimeout", "5000000", 0); // Socket timeout in microseconds (5 seconds)
// Open input
ret = avformat_open_input(&input_ctx, rtsp_url, NULL, &opts);
if (ret < 0) {
log_error(ret, "Failed to open input");
goto cleanup;
}
// Find stream info
ret = avformat_find_stream_info(input_ctx, NULL);
if (ret < 0) {
log_error(ret, "Failed to find stream info");
goto cleanup;
}
// Print input stream info
printf("Input format: %s\n", input_ctx->iformat->name);
printf("Number of streams: %d\n", input_ctx->nb_streams);
// Find video and audio streams
int video_stream_idx = -1;
int audio_stream_idx = -1;
for (unsigned int i = 0; i < input_ctx->nb_streams; i++) {
AVStream *stream = input_ctx->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_stream_idx < 0) {
video_stream_idx = i;
printf("Found video stream: %d\n", i);
printf(" Codec: %s\n", avcodec_get_name(stream->codecpar->codec_id));
printf(" Resolution: %dx%d\n", stream->codecpar->width, stream->codecpar->height);
if (stream->avg_frame_rate.num && stream->avg_frame_rate.den) {
printf(" Frame rate: %.2f fps\n",
(float)stream->avg_frame_rate.num / stream->avg_frame_rate.den);
}
} else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_stream_idx < 0) {
audio_stream_idx = i;
printf("Found audio stream: %d\n", i);
printf(" Codec: %s\n", avcodec_get_name(stream->codecpar->codec_id));
printf(" Sample rate: %d Hz\n", stream->codecpar->sample_rate);
// Handle channel count for different FFmpeg versions
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 37, 100)
// For FFmpeg 5.0 and newer
printf(" Channels: %d\n", stream->codecpar->ch_layout.nb_channels);
#else
// For older FFmpeg versions
printf(" Channels: %d\n", stream->codecpar->channels);
#endif
}
}
if (video_stream_idx < 0) {
fprintf(stderr, "No video stream found\n");
goto cleanup;
}
// Output context
AVFormatContext *output_ctx = NULL;
// Create output context
ret = avformat_alloc_output_context2(&output_ctx, NULL, "mp4", output_file);
if (ret < 0 || !output_ctx) {
log_error(ret, "Failed to create output context");
goto cleanup;
}
// Create output streams (copy from input)
AVStream *out_video_stream = NULL;
AVStream *out_audio_stream = NULL;
// Add video stream
out_video_stream = avformat_new_stream(output_ctx, NULL);
if (!out_video_stream) {
fprintf(stderr, "Failed to create output video stream\n");
goto cleanup;
}
// Copy video codec parameters
ret = avcodec_parameters_copy(out_video_stream->codecpar,
input_ctx->streams[video_stream_idx]->codecpar);
if (ret < 0) {
log_error(ret, "Failed to copy video codec parameters");
goto cleanup;
}
// Set video stream time base
out_video_stream->time_base = input_ctx->streams[video_stream_idx]->time_base;
// Add audio stream if available
if (audio_stream_idx >= 0) {
out_audio_stream = avformat_new_stream(output_ctx, NULL);
if (!out_audio_stream) {
fprintf(stderr, "Failed to create output audio stream\n");
goto cleanup;
}
// Copy audio codec parameters
ret = avcodec_parameters_copy(out_audio_stream->codecpar,
input_ctx->streams[audio_stream_idx]->codecpar);
if (ret < 0) {
log_error(ret, "Failed to copy audio codec parameters");
goto cleanup;
}
// Set audio stream time base
out_audio_stream->time_base = input_ctx->streams[audio_stream_idx]->time_base;
}
// Set output options for fast start
AVDictionary *out_opts = NULL;
av_dict_set(&out_opts, "movflags", "+faststart", 0);
// Open output file
ret = avio_open(&output_ctx->pb, output_file, AVIO_FLAG_WRITE);
if (ret < 0) {
log_error(ret, "Failed to open output file");
goto cleanup;
}
// Write file header
ret = avformat_write_header(output_ctx, &out_opts);
if (ret < 0) {
log_error(ret, "Failed to write header");
goto cleanup;
}
// Packet for reading/writing
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
// Timestamp tracking
int64_t start_time = av_gettime();
int64_t first_video_dts = AV_NOPTS_VALUE;
int64_t first_audio_dts = AV_NOPTS_VALUE;
printf("Recording started...\n");
// Main recording loop
while (!stop_recording) {
// Check if we've reached the duration limit
if (duration > 0 && (av_gettime() - start_time) / 1000000 >= duration) {
printf("Reached duration limit of %d seconds\n", duration);
break;
}
// Read packet
ret = av_read_frame(input_ctx, &pkt);
if (ret < 0) {
if (ret == AVERROR_EOF) {
printf("End of stream reached\n");
break;
} else if (ret != AVERROR(EAGAIN)) {
log_error(ret, "Error reading frame");
break;
}
// EAGAIN means try again, so we continue
av_usleep(10000); // Sleep 10ms to avoid busy waiting
continue;
}
// Process video packets
if (pkt.stream_index == video_stream_idx) {
// Initialize first DTS if not set
if (first_video_dts == AV_NOPTS_VALUE && pkt.dts != AV_NOPTS_VALUE) {
first_video_dts = pkt.dts;
printf("First video DTS: %lld\n", (long long)first_video_dts);
}
// Adjust timestamps
if (pkt.dts != AV_NOPTS_VALUE && first_video_dts != AV_NOPTS_VALUE) {
pkt.dts -= first_video_dts;
if (pkt.dts < 0) pkt.dts = 0;
}
if (pkt.pts != AV_NOPTS_VALUE && first_video_dts != AV_NOPTS_VALUE) {
pkt.pts -= first_video_dts;
if (pkt.pts < 0) pkt.pts = 0;
}
// Set output stream index
pkt.stream_index = out_video_stream->index;
// Write packet
ret = av_interleaved_write_frame(output_ctx, &pkt);
if (ret < 0) {
log_error(ret, "Error writing video frame");
}
}
// Process audio packets
else if (audio_stream_idx >= 0 && pkt.stream_index == audio_stream_idx && out_audio_stream) {
// Initialize first audio DTS if not set
if (first_audio_dts == AV_NOPTS_VALUE && pkt.dts != AV_NOPTS_VALUE) {
first_audio_dts = pkt.dts;
printf("First audio DTS: %lld\n", (long long)first_audio_dts);
}
// Use a static variable to track the last audio DTS we've seen
static int64_t last_audio_dts = 0;
static int64_t last_audio_pts = 0;
static int audio_packet_count = 0;
// For the first audio packet, initialize our tracking variables
if (audio_packet_count == 0) {
if (pkt.dts != AV_NOPTS_VALUE && first_audio_dts != AV_NOPTS_VALUE) {
last_audio_dts = pkt.dts - first_audio_dts;
if (last_audio_dts < 0) last_audio_dts = 0;
} else {
last_audio_dts = 0;
}
if (pkt.pts != AV_NOPTS_VALUE && first_audio_dts != AV_NOPTS_VALUE) {
last_audio_pts = pkt.pts - first_audio_dts;
if (last_audio_pts < 0) last_audio_pts = 0;
} else {
last_audio_pts = 0;
}
audio_packet_count++;
}
// Adjust timestamps to ensure monotonic increase
if (pkt.dts != AV_NOPTS_VALUE && first_audio_dts != AV_NOPTS_VALUE) {
int64_t new_dts = pkt.dts - first_audio_dts;
if (new_dts < 0) new_dts = 0;
// Ensure DTS is monotonically increasing
if (new_dts <= last_audio_dts) {
new_dts = last_audio_dts + 1;
}
pkt.dts = new_dts;
last_audio_dts = new_dts;
} else {
// If DTS is not set, use last_audio_dts + 1
pkt.dts = last_audio_dts + 1;
last_audio_dts = pkt.dts;
}
if (pkt.pts != AV_NOPTS_VALUE && first_audio_dts != AV_NOPTS_VALUE) {
int64_t new_pts = pkt.pts - first_audio_dts;
if (new_pts < 0) new_pts = 0;
// Ensure PTS is monotonically increasing
if (new_pts <= last_audio_pts) {
new_pts = last_audio_pts + 1;
}
// Ensure PTS >= DTS
if (new_pts < pkt.dts) {
new_pts = pkt.dts;
}
pkt.pts = new_pts;
last_audio_pts = new_pts;
} else {
// If PTS is not set, use DTS
pkt.pts = pkt.dts;
last_audio_pts = pkt.pts;
}
// Set output stream index
pkt.stream_index = out_audio_stream->index;
// Write packet
ret = av_interleaved_write_frame(output_ctx, &pkt);
if (ret < 0) {
log_error(ret, "Error writing audio frame");
} else {
audio_packet_count++;
}
}
// Unref packet
av_packet_unref(&pkt);
// Print progress every second
static time_t last_progress = 0;
time_t now = time(NULL);
if (now != last_progress) {
printf("\rRecording: %ld/%d seconds...", (now - (start_time / 1000000)), duration);
fflush(stdout);
last_progress = now;
}
}
printf("\nRecording complete\n");
// Write trailer
if (output_ctx) {
ret = av_write_trailer(output_ctx);
if (ret < 0) {
log_error(ret, "Failed to write trailer");
}
}
cleanup:
// Close input
if (input_ctx) {
avformat_close_input(&input_ctx);
}
// Close output
if (output_ctx) {
if (output_ctx->pb) {
avio_closep(&output_ctx->pb);
}
avformat_free_context(output_ctx);
}
// Free dictionaries
av_dict_free(&opts);
// Cleanup FFmpeg
avformat_network_deinit();
printf("Done\n");
return ret < 0 ? 1 : 0;
}