... | ... |
@@ -871,6 +871,11 @@ separated by @code{/}. If the stream specifier is not specified, the |
871 | 871 |
bistream filters will be applied to all streams in the output. |
872 | 872 |
|
873 | 873 |
Several bitstream filters can be specified, separated by ",". |
874 |
+ |
|
875 |
+@item select |
|
876 |
+Select the streams that should be mapped to the slave output, |
|
877 |
+specified by a stream specifier. If not specified, this defaults to |
|
878 |
+all the input streams. |
|
874 | 879 |
@end table |
875 | 880 |
|
876 | 881 |
Example: encode something and both archive it in a WebM file and stream it |
... | ... |
@@ -30,6 +30,10 @@ |
30 | 30 |
typedef struct { |
31 | 31 |
AVFormatContext *avf; |
32 | 32 |
AVBitStreamFilterContext **bsfs; ///< bitstream filters per stream |
33 |
+ |
|
34 |
+ /** map from input to output streams indexes, |
|
35 |
+ * disabled output streams are set to -1 */ |
|
36 |
+ int *stream_map; |
|
33 | 37 |
} TeeSlave; |
34 | 38 |
|
35 | 39 |
typedef struct TeeContext { |
... | ... |
@@ -134,24 +138,54 @@ static int open_slave(AVFormatContext *avf, char *slave, TeeSlave *tee_slave) |
134 | 134 |
AVDictionary *options = NULL; |
135 | 135 |
AVDictionaryEntry *entry; |
136 | 136 |
char *filename; |
137 |
- char *format = NULL; |
|
137 |
+ char *format = NULL, *select = NULL; |
|
138 | 138 |
AVFormatContext *avf2 = NULL; |
139 | 139 |
AVStream *st, *st2; |
140 |
+ int stream_count; |
|
140 | 141 |
|
141 | 142 |
if ((ret = parse_slave_options(avf, slave, &options, &filename)) < 0) |
142 | 143 |
return ret; |
143 |
- if ((entry = av_dict_get(options, "f", NULL, 0))) { |
|
144 |
- format = entry->value; |
|
145 |
- entry->value = NULL; /* prevent it from being freed */ |
|
146 |
- av_dict_set(&options, "f", NULL, 0); |
|
147 |
- } |
|
144 |
+ |
|
145 |
+#define STEAL_OPTION(option, field) do { \ |
|
146 |
+ if ((entry = av_dict_get(options, option, NULL, 0))) { \ |
|
147 |
+ field = entry->value; \ |
|
148 |
+ entry->value = NULL; /* prevent it from being freed */ \ |
|
149 |
+ av_dict_set(&options, option, NULL, 0); \ |
|
150 |
+ } \ |
|
151 |
+ } while (0) |
|
152 |
+ |
|
153 |
+ STEAL_OPTION("f", format); |
|
154 |
+ STEAL_OPTION("select", select); |
|
148 | 155 |
|
149 | 156 |
ret = avformat_alloc_output_context2(&avf2, NULL, format, filename); |
150 | 157 |
if (ret < 0) |
151 | 158 |
goto end; |
152 | 159 |
|
160 |
+ tee_slave->stream_map = av_calloc(avf->nb_streams, sizeof(*tee_slave->stream_map)); |
|
161 |
+ if (!tee_slave->stream_map) { |
|
162 |
+ ret = AVERROR(ENOMEM); |
|
163 |
+ goto end; |
|
164 |
+ } |
|
165 |
+ |
|
166 |
+ stream_count = 0; |
|
153 | 167 |
for (i = 0; i < avf->nb_streams; i++) { |
154 | 168 |
st = avf->streams[i]; |
169 |
+ if (select) { |
|
170 |
+ ret = avformat_match_stream_specifier(avf, avf->streams[i], select); |
|
171 |
+ if (ret < 0) { |
|
172 |
+ av_log(avf, AV_LOG_ERROR, |
|
173 |
+ "Invalid stream specifier '%s' for output '%s'\n", |
|
174 |
+ select, slave); |
|
175 |
+ goto end; |
|
176 |
+ } |
|
177 |
+ |
|
178 |
+ if (ret == 0) { /* no match */ |
|
179 |
+ tee_slave->stream_map[i] = -1; |
|
180 |
+ continue; |
|
181 |
+ } |
|
182 |
+ } |
|
183 |
+ tee_slave->stream_map[i] = stream_count++; |
|
184 |
+ |
|
155 | 185 |
if (!(st2 = avformat_new_stream(avf2, NULL))) { |
156 | 186 |
ret = AVERROR(ENOMEM); |
157 | 187 |
goto end; |
... | ... |
@@ -266,6 +300,7 @@ static void close_slaves(AVFormatContext *avf) |
266 | 266 |
bsf = bsf_next; |
267 | 267 |
} |
268 | 268 |
} |
269 |
+ av_freep(&tee->slaves[i].stream_map); |
|
269 | 270 |
|
270 | 271 |
avio_close(avf2->pb); |
271 | 272 |
avf2->pb = NULL; |
... | ... |
@@ -329,6 +364,15 @@ static int tee_write_header(AVFormatContext *avf) |
329 | 329 |
} |
330 | 330 |
|
331 | 331 |
tee->nb_slaves = nb_slaves; |
332 |
+ |
|
333 |
+ for (i = 0; i < avf->nb_streams; i++) { |
|
334 |
+ int j, mapped = 0; |
|
335 |
+ for (j = 0; j < tee->nb_slaves; j++) |
|
336 |
+ mapped += tee->slaves[j].stream_map[i] >= 0; |
|
337 |
+ if (!mapped) |
|
338 |
+ av_log(avf, AV_LOG_WARNING, "Input stream #%d is not mapped " |
|
339 |
+ "to any slave.\n", i); |
|
340 |
+ } |
|
332 | 341 |
return 0; |
333 | 342 |
|
334 | 343 |
fail: |
... | ... |
@@ -408,29 +452,30 @@ static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt) |
408 | 408 |
AVPacket pkt2; |
409 | 409 |
int ret_all = 0, ret; |
410 | 410 |
unsigned i, s; |
411 |
+ int s2; |
|
411 | 412 |
AVRational tb, tb2; |
412 | 413 |
|
413 | 414 |
for (i = 0; i < tee->nb_slaves; i++) { |
414 | 415 |
avf2 = tee->slaves[i].avf; |
415 | 416 |
s = pkt->stream_index; |
416 |
- if (s >= avf2->nb_streams) { |
|
417 |
- if (!ret_all) |
|
418 |
- ret_all = AVERROR(EINVAL); |
|
417 |
+ s2 = tee->slaves[i].stream_map[s]; |
|
418 |
+ if (s2 < 0) |
|
419 | 419 |
continue; |
420 |
- } |
|
420 |
+ |
|
421 | 421 |
if ((ret = av_copy_packet(&pkt2, pkt)) < 0 || |
422 | 422 |
(ret = av_dup_packet(&pkt2))< 0) |
423 | 423 |
if (!ret_all) { |
424 | 424 |
ret = ret_all; |
425 | 425 |
continue; |
426 | 426 |
} |
427 |
- tb = avf ->streams[s]->time_base; |
|
428 |
- tb2 = avf2->streams[s]->time_base; |
|
427 |
+ tb = avf ->streams[s ]->time_base; |
|
428 |
+ tb2 = avf2->streams[s2]->time_base; |
|
429 | 429 |
pkt2.pts = av_rescale_q(pkt->pts, tb, tb2); |
430 | 430 |
pkt2.dts = av_rescale_q(pkt->dts, tb, tb2); |
431 | 431 |
pkt2.duration = av_rescale_q(pkt->duration, tb, tb2); |
432 |
+ pkt2.stream_index = s2; |
|
432 | 433 |
|
433 |
- filter_packet(avf2, &pkt2, avf2, tee->slaves[i].bsfs[s]); |
|
434 |
+ filter_packet(avf2, &pkt2, avf2, tee->slaves[i].bsfs[s2]); |
|
434 | 435 |
if ((ret = av_interleaved_write_frame(avf2, &pkt2)) < 0) |
435 | 436 |
if (!ret_all) |
436 | 437 |
ret_all = ret; |
... | ... |
@@ -31,7 +31,7 @@ |
31 | 31 |
|
32 | 32 |
#define LIBAVFORMAT_VERSION_MAJOR 55 |
33 | 33 |
#define LIBAVFORMAT_VERSION_MINOR 14 |
34 |
-#define LIBAVFORMAT_VERSION_MICRO 100 |
|
34 |
+#define LIBAVFORMAT_VERSION_MICRO 101 |
|
35 | 35 |
|
36 | 36 |
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ |
37 | 37 |
LIBAVFORMAT_VERSION_MINOR, \ |