diff --git a/doc/indevs.texi b/doc/indevs.texi index ad823abad4..abc4cc3944 100644 --- a/doc/indevs.texi +++ b/doc/indevs.texi @@ -495,6 +495,14 @@ generated by the device. The first unlabelled output is automatically assigned to the "out0" label, but all the others need to be specified explicitly. +The suffix "+subcc" can be appended to the output label to create an extra +stream with the closed captions packets attached to that output +(experimental; only for EIA-608 / CEA-708 for now). +The subcc streams are created after all the normal streams, in the order of +the corresponding stream. +For example, if there is "out19+subcc", "out7+subcc" and up to "out42", the +stream #43 is subcc for stream #7 and stream #44 is subcc for stream #19. + If not specified defaults to the filename specified for the input device. @@ -541,6 +549,12 @@ Read an audio stream and a video stream and play it back with ffplay -f lavfi "movie=test.avi[out0];amovie=test.wav[out1]" @end example +@item +Dump decoded frames to images and closed captions to a file (experimental): +@example +ffmpeg -f lavfi -i "movie=test.ts[out0+subcc]" -map v frame%08d.png -map s -c copy -f rawvideo subcc.bin +@end example + @end itemize @section libcdio diff --git a/libavdevice/lavfi.c b/libavdevice/lavfi.c index 1398ece508..d0e6bf5f4d 100644 --- a/libavdevice/lavfi.c +++ b/libavdevice/lavfi.c @@ -51,7 +51,10 @@ typedef struct { int *sink_stream_map; int *sink_eof; int *stream_sink_map; + int *sink_stream_subcc_map; AVFrame *decoded_frame; + int nb_sinks; + AVPacket subcc_packet; } LavfiContext; static int *create_all_formats(int n) @@ -82,6 +85,7 @@ av_cold static int lavfi_read_close(AVFormatContext *avctx) av_freep(&lavfi->sink_stream_map); av_freep(&lavfi->sink_eof); av_freep(&lavfi->stream_sink_map); + av_freep(&lavfi->sink_stream_subcc_map); av_freep(&lavfi->sinks); avfilter_graph_free(&lavfi->graph); av_frame_free(&lavfi->decoded_frame); @@ -89,6 +93,27 @@ av_cold static int lavfi_read_close(AVFormatContext *avctx) return 0; } +static int create_subcc_streams(AVFormatContext *avctx) +{ + LavfiContext *lavfi = avctx->priv_data; + AVStream *st; + int stream_idx, sink_idx; + + for (stream_idx = 0; stream_idx < lavfi->nb_sinks; stream_idx++) { + sink_idx = lavfi->stream_sink_map[stream_idx]; + if (lavfi->sink_stream_subcc_map[sink_idx]) { + lavfi->sink_stream_subcc_map[sink_idx] = avctx->nb_streams; + if (!(st = avformat_new_stream(avctx, NULL))) + return AVERROR(ENOMEM); + st->codec->codec_id = AV_CODEC_ID_EIA_608; + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + } else { + lavfi->sink_stream_subcc_map[sink_idx] = -1; + } + } + return 0; +} + av_cold static int lavfi_read_header(AVFormatContext *avctx) { LavfiContext *lavfi = avctx->priv_data; @@ -153,6 +178,7 @@ av_cold static int lavfi_read_header(AVFormatContext *avctx) /* count the outputs */ for (n = 0, inout = output_links; inout; n++, inout = inout->next); + lavfi->nb_sinks = n; if (!(lavfi->sink_stream_map = av_malloc(sizeof(int) * n))) FAIL(AVERROR(ENOMEM)); @@ -160,6 +186,8 @@ av_cold static int lavfi_read_header(AVFormatContext *avctx) FAIL(AVERROR(ENOMEM)); if (!(lavfi->stream_sink_map = av_malloc(sizeof(int) * n))) FAIL(AVERROR(ENOMEM)); + if (!(lavfi->sink_stream_subcc_map = av_malloc(sizeof(int) * n))) + FAIL(AVERROR(ENOMEM)); for (i = 0; i < n; i++) lavfi->stream_sink_map[i] = -1; @@ -167,14 +195,22 @@ av_cold static int lavfi_read_header(AVFormatContext *avctx) /* parse the output link names - they need to be of the form out0, out1, ... * create a mapping between them and the streams */ for (i = 0, inout = output_links; inout; i++, inout = inout->next) { - int stream_idx; - if (!strcmp(inout->name, "out")) - stream_idx = 0; - else if (sscanf(inout->name, "out%d\n", &stream_idx) != 1) { + int stream_idx = 0, suffix = 0, use_subcc = 0; + sscanf(inout->name, "out%n%d%n", &suffix, &stream_idx, &suffix); + if (!suffix) { av_log(avctx, AV_LOG_ERROR, "Invalid outpad name '%s'\n", inout->name); FAIL(AVERROR(EINVAL)); } + if (inout->name[suffix]) { + if (!strcmp(inout->name + suffix, "+subcc")) { + use_subcc = 1; + } else { + av_log(avctx, AV_LOG_ERROR, + "Invalid outpad suffix '%s'\n", inout->name); + FAIL(AVERROR(EINVAL)); + } + } if ((unsigned)stream_idx >= n) { av_log(avctx, AV_LOG_ERROR, @@ -192,6 +228,7 @@ av_cold static int lavfi_read_header(AVFormatContext *avctx) } lavfi->sink_stream_map[i] = stream_idx; lavfi->stream_sink_map[stream_idx] = i; + lavfi->sink_stream_subcc_map[i] = !!use_subcc; } /* for each open output create a corresponding stream */ @@ -203,7 +240,7 @@ av_cold static int lavfi_read_header(AVFormatContext *avctx) } /* create a sink for each output and connect them to the graph */ - lavfi->sinks = av_malloc_array(avctx->nb_streams, sizeof(AVFilterContext *)); + lavfi->sinks = av_malloc_array(lavfi->nb_sinks, sizeof(AVFilterContext *)); if (!lavfi->sinks) FAIL(AVERROR(ENOMEM)); @@ -267,7 +304,7 @@ av_cold static int lavfi_read_header(AVFormatContext *avctx) } /* fill each stream with the information in the corresponding sink */ - for (i = 0; i < avctx->nb_streams; i++) { + for (i = 0; i < lavfi->nb_sinks; i++) { AVFilterLink *link = lavfi->sinks[lavfi->stream_sink_map[i]]->inputs[0]; AVStream *st = avctx->streams[i]; st->codec->codec_type = link->type; @@ -298,6 +335,9 @@ av_cold static int lavfi_read_header(AVFormatContext *avctx) } } + if ((ret = create_subcc_streams(avctx)) < 0) + FAIL(ret); + if (!(lavfi->decoded_frame = av_frame_alloc())) FAIL(AVERROR(ENOMEM)); @@ -310,6 +350,30 @@ end: return ret; } +static int create_subcc_packet(AVFormatContext *avctx, AVFrame *frame, + int sink_idx) +{ + LavfiContext *lavfi = avctx->priv_data; + AVFrameSideData *sd; + int stream_idx, i, ret; + + if ((stream_idx = lavfi->sink_stream_subcc_map[sink_idx]) < 0) + return 0; + for (i = 0; i < frame->nb_side_data; i++) + if (frame->side_data[i]->type == AV_FRAME_DATA_A53_CC) + break; + if (i >= frame->nb_side_data) + return 0; + sd = frame->side_data[i]; + if ((ret = av_new_packet(&lavfi->subcc_packet, sd->size)) < 0) + return ret; + memcpy(lavfi->subcc_packet.data, sd->data, sd->size); + lavfi->subcc_packet.stream_index = stream_idx; + lavfi->subcc_packet.pts = frame->pts; + lavfi->subcc_packet.pos = av_frame_get_pkt_pos(frame); + return 0; +} + static int lavfi_read_packet(AVFormatContext *avctx, AVPacket *pkt) { LavfiContext *lavfi = avctx->priv_data; @@ -321,9 +385,17 @@ static int lavfi_read_packet(AVFormatContext *avctx, AVPacket *pkt) int ret, i; int size = 0; + if (lavfi->subcc_packet.size) { + *pkt = lavfi->subcc_packet; + av_init_packet(&lavfi->subcc_packet); + lavfi->subcc_packet.size = 0; + lavfi->subcc_packet.data = NULL; + return pkt->size; + } + /* iterate through all the graph sinks. Select the sink with the * minimum PTS */ - for (i = 0; i < avctx->nb_streams; i++) { + for (i = 0; i < lavfi->nb_sinks; i++) { AVRational tb = lavfi->sinks[i]->inputs[0]->time_base; double d; int ret; @@ -397,6 +469,12 @@ static int lavfi_read_packet(AVFormatContext *avctx, AVPacket *pkt) av_bprint_finalize(&meta_buf, NULL); } + if ((ret = create_subcc_packet(avctx, frame, min_pts_sink_idx)) < 0) { + av_frame_unref(frame); + av_packet_unref(pkt); + return ret; + } + pkt->stream_index = stream_idx; pkt->pts = frame->pts; pkt->pos = av_frame_get_pkt_pos(frame);