diff --git a/Makefile b/Makefile index 7317f254fb..5437c0bea7 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ OBJS-ffmpeg += ffmpeg_opt.o ffmpeg_filter.o OBJS-ffmpeg-$(HAVE_VDPAU_X11) += ffmpeg_vdpau.o TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 HOSTPROGS := $(TESTTOOLS:%=tests/%) doc/print_options -TOOLS = qt-faststart trasher +TOOLS = qt-faststart trasher uncoded_frame TOOLS-$(CONFIG_ZLIB) += cws2fws FFLIBS-$(CONFIG_AVDEVICE) += avdevice @@ -61,6 +61,8 @@ $(TOOLS): %$(EXESUF): %.o $(EXEOBJS) $(LD) $(LDFLAGS) $(LD_O) $^ $(ELIBS) tools/cws2fws$(EXESUF): ELIBS = $(ZLIB) +tools/uncoded_frame$(EXESUF): $(FF_DEP_LIBS) +tools/uncoded_frame$(EXESUF): ELIBS = $(FF_EXTRALIBS) config.h: .config .config: $(wildcard $(FFLIBS:%=$(SRC_PATH)/lib%/all*.c)) diff --git a/doc/APIchanges b/doc/APIchanges index 6ddf5b3cf7..77a9e4eacd 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -15,6 +15,9 @@ libavutil: 2012-10-22 API changes, most recent first: +2014-02-11 - 1b05ac2 - lavf 55.32.100 - avformat.h + Add av_write_uncoded_frame() and av_interleaved_write_uncoded_frame(). + 2014-02-04 - 3adb5f8 / d9ae103 - lavf 55.30.100 / 55.11.0 - avformat.h Add AVFormatContext.max_interleave_delta for controlling amount of buffering when interleaving. diff --git a/libavdevice/alsa-audio-enc.c b/libavdevice/alsa-audio-enc.c index 83e1d2f85f..bf56fc46d7 100644 --- a/libavdevice/alsa-audio-enc.c +++ b/libavdevice/alsa-audio-enc.c @@ -113,6 +113,24 @@ static int audio_write_packet(AVFormatContext *s1, AVPacket *pkt) return 0; } +static int audio_write_frame(AVFormatContext *s1, int stream_index, + AVFrame **frame, unsigned flags) +{ + AlsaData *s = s1->priv_data; + AVPacket pkt; + + /* ff_alsa_open() should have accepted only supported formats */ + if ((flags & AV_WRITE_UNCODED_FRAME_QUERY)) + return av_sample_fmt_is_planar(s1->streams[stream_index]->codec->sample_fmt) ? + AVERROR(EINVAL) : 0; + /* set only used fields */ + pkt.data = (*frame)->data[0]; + pkt.size = (*frame)->nb_samples * s->frame_size; + pkt.dts = (*frame)->pkt_dts; + pkt.duration = av_frame_get_pkt_duration(*frame); + return audio_write_packet(s1, &pkt); +} + static void audio_get_output_timestamp(AVFormatContext *s1, int stream, int64_t *dts, int64_t *wall) @@ -133,6 +151,7 @@ AVOutputFormat ff_alsa_muxer = { .write_header = audio_write_header, .write_packet = audio_write_packet, .write_trailer = ff_alsa_close, + .write_uncoded_frame = audio_write_frame, .get_output_timestamp = audio_get_output_timestamp, .flags = AVFMT_NOFILE, }; diff --git a/libavdevice/version.h b/libavdevice/version.h index 0dedc73551..956ed74643 100644 --- a/libavdevice/version.h +++ b/libavdevice/version.h @@ -28,7 +28,7 @@ #include "libavutil/version.h" #define LIBAVDEVICE_VERSION_MAJOR 55 -#define LIBAVDEVICE_VERSION_MINOR 8 +#define LIBAVDEVICE_VERSION_MINOR 9 #define LIBAVDEVICE_VERSION_MICRO 100 #define LIBAVDEVICE_VERSION_INT AV_VERSION_INT(LIBAVDEVICE_VERSION_MAJOR, \ diff --git a/libavdevice/xv.c b/libavdevice/xv.c index a4b44c8eda..89d6575f6c 100644 --- a/libavdevice/xv.c +++ b/libavdevice/xv.c @@ -36,6 +36,7 @@ #include "libavutil/opt.h" #include "libavutil/pixdesc.h" #include "libavutil/imgutils.h" +#include "libavformat/internal.h" #include "avdevice.h" typedef struct { @@ -197,23 +198,19 @@ static int xv_write_header(AVFormatContext *s) return ret; } -static int xv_write_packet(AVFormatContext *s, AVPacket *pkt) +static int write_picture(AVFormatContext *s, AVPicture *pict) { XVContext *xv = s->priv_data; XvImage *img = xv->yuv_image; XWindowAttributes window_attrs; - AVPicture pict; - AVCodecContext *ctx = s->streams[0]->codec; uint8_t *data[3] = { img->data + img->offsets[0], img->data + img->offsets[1], img->data + img->offsets[2] }; - avpicture_fill(&pict, pkt->data, ctx->pix_fmt, ctx->width, ctx->height); - av_image_copy(data, img->pitches, (const uint8_t **)pict.data, pict.linesize, + av_image_copy(data, img->pitches, (const uint8_t **)pict->data, pict->linesize, xv->image_format, img->width, img->height); - XGetWindowAttributes(xv->display, xv->window, &window_attrs); if (XvShmPutImage(xv->display, xv->xv_port, xv->window, xv->gc, xv->yuv_image, 0, 0, xv->image_width, xv->image_height, 0, 0, @@ -224,6 +221,24 @@ static int xv_write_packet(AVFormatContext *s, AVPacket *pkt) return 0; } +static int xv_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVPicture pict; + AVCodecContext *ctx = s->streams[0]->codec; + + avpicture_fill(&pict, pkt->data, ctx->pix_fmt, ctx->width, ctx->height); + return write_picture(s, &pict); +} + +static int xv_write_frame(AVFormatContext *s, int stream_index, AVFrame **frame, + unsigned flags) +{ + /* xv_write_header() should have accepted only supported formats */ + if ((flags & AV_WRITE_UNCODED_FRAME_QUERY)) + return 0; + return write_picture(s, (AVPicture *)*frame); +} + #define OFFSET(x) offsetof(XVContext, x) static const AVOption options[] = { { "display_name", "set display name", OFFSET(display_name), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM }, @@ -250,6 +265,7 @@ AVOutputFormat ff_xv_muxer = { .video_codec = AV_CODEC_ID_RAWVIDEO, .write_header = xv_write_header, .write_packet = xv_write_packet, + .write_uncoded_frame = xv_write_frame, .write_trailer = xv_write_trailer, .flags = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS, .priv_class = &xv_class, diff --git a/libavformat/Makefile b/libavformat/Makefile index 72ef8c7c44..ff98b9b29f 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -388,6 +388,7 @@ OBJS-$(CONFIG_TRUEHD_MUXER) += rawenc.o OBJS-$(CONFIG_TTA_DEMUXER) += tta.o apetag.o img2.o OBJS-$(CONFIG_TTY_DEMUXER) += tty.o sauce.o OBJS-$(CONFIG_TXD_DEMUXER) += txd.o +OBJS-$(CONFIG_UNCODEDFRAMECRC_MUXER) += uncodedframecrcenc.o framehash.o OBJS-$(CONFIG_VC1_DEMUXER) += rawdec.o OBJS-$(CONFIG_VC1_MUXER) += rawenc.o OBJS-$(CONFIG_VC1T_DEMUXER) += vc1test.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 195e6fb168..6ee2f99e5b 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -284,6 +284,7 @@ void av_register_all(void) REGISTER_DEMUXER (TTA, tta); REGISTER_DEMUXER (TXD, txd); REGISTER_DEMUXER (TTY, tty); + REGISTER_MUXER (UNCODEDFRAMECRC, uncodedframecrc); REGISTER_MUXDEMUX(VC1, vc1); REGISTER_MUXDEMUX(VC1T, vc1t); REGISTER_DEMUXER (VIVO, vivo); diff --git a/libavformat/avformat.h b/libavformat/avformat.h index 2d62b5b4f0..2667b37b5c 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -512,6 +512,17 @@ typedef struct AVOutputFormat { */ int (*control_message)(struct AVFormatContext *s, int type, void *data, size_t data_size); + + /** + * Write an uncoded AVFrame. + * + * See av_write_uncoded_frame() for details. + * + * The library will free *frame afterwards, but the muxer can prevent it + * by setting the pointer to NULL. + */ + int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index, + AVFrame **frame, unsigned flags); } AVOutputFormat; /** * @} @@ -2092,6 +2103,44 @@ int av_write_frame(AVFormatContext *s, AVPacket *pkt); */ int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt); +/** + * Write a uncoded frame to an output media file. + * + * The frame must be correctly interleaved according to the container + * specification; if not, then av_interleaved_write_frame() must be used. + * + * See av_interleaved_write_frame() for details. + */ +int av_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame); + +/** + * Write a uncoded frame to an output media file. + * + * If the muxer supports it, this function allows to write an AVFrame + * structure directly, without encoding it into a packet. + * It is mostly useful for devices and similar special muxers that use raw + * video or PCM data and will not serialize it into a byte stream. + * + * To test whether it is possible to use it with a given muxer and stream, + * use av_write_uncoded_frame_query(). + * + * The caller gives up ownership of the frame and must not access it + * afterwards. + * + * @return >=0 for success, a negative code on error + */ +int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame); + +/** + * Test whether a muxer supports uncoded frame. + * + * @return >=0 if an uncoded frame can be written to that muxer and stream, + * <0 if not + */ +int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index); + /** * Write the stream trailer to an output media file and free the * file private data. diff --git a/libavformat/internal.h b/libavformat/internal.h index a840f8af05..f19cebf22e 100644 --- a/libavformat/internal.h +++ b/libavformat/internal.h @@ -398,4 +398,18 @@ int ff_rfps_add_frame(AVFormatContext *ic, AVStream *st, int64_t dts); void ff_rfps_calculate(AVFormatContext *ic); +/** + * Flags for AVFormatContext.write_uncoded_frame() + */ +enum AVWriteUncodedFrameFlags { + + /** + * Query whether the feature is possible on this stream. + * The frame argument is ignored. + */ + AV_WRITE_UNCODED_FRAME_QUERY = 0x0001, + +}; + + #endif /* AVFORMAT_INTERNAL_H */ diff --git a/libavformat/mux.c b/libavformat/mux.c index 14e72e8cd8..c535c82a61 100644 --- a/libavformat/mux.c +++ b/libavformat/mux.c @@ -417,6 +417,15 @@ int avformat_write_header(AVFormatContext *s, AVDictionary **options) return 0; } +#define AV_PKT_FLAG_UNCODED_FRAME 0x2000 + +/* Note: using sizeof(AVFrame) from outside lavu is unsafe in general, but + it is only being used internally to this file as a consistency check. + The value is chosen to be very unlikely to appear on its own and to cause + immediate failure if used anywhere as a real size. */ +#define UNCODED_FRAME_PACKET_SIZE (INT_MIN / 3 * 2 + (int)sizeof(AVFrame)) + + //FIXME merge with compute_pkt_fields static int compute_pkt_fields2(AVFormatContext *s, AVStream *st, AVPacket *pkt) { @@ -482,7 +491,9 @@ static int compute_pkt_fields2(AVFormatContext *s, AVStream *st, AVPacket *pkt) /* update pts */ switch (st->codec->codec_type) { case AVMEDIA_TYPE_AUDIO: - frame_size = ff_get_audio_frame_size(st->codec, pkt->size, 1); + frame_size = (pkt->flags & AV_PKT_FLAG_UNCODED_FRAME) ? + ((AVFrame *)pkt->data)->nb_samples : + ff_get_audio_frame_size(st->codec, pkt->size, 1); /* HACK/FIXME, we skip the initial 0 size packets as they are most * likely equal to the encoder delay, but it would be better if we @@ -549,7 +560,14 @@ static int write_packet(AVFormatContext *s, AVPacket *pkt) } did_split = av_packet_split_side_data(pkt); - ret = s->oformat->write_packet(s, pkt); + if ((pkt->flags & AV_PKT_FLAG_UNCODED_FRAME)) { + AVFrame *frame = (AVFrame *)pkt->data; + av_assert0(pkt->size == UNCODED_FRAME_PACKET_SIZE); + ret = s->oformat->write_uncoded_frame(s, pkt->stream_index, &frame, 0); + av_frame_free(&frame); + } else { + ret = s->oformat->write_packet(s, pkt); + } if (s->flush_packets && s->pb && ret >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS) avio_flush(s->pb); @@ -632,8 +650,13 @@ FF_DISABLE_DEPRECATION_WARNINGS FF_ENABLE_DEPRECATION_WARNINGS #endif pkt->buf = NULL; - av_dup_packet(&this_pktl->pkt); // duplicate the packet if it uses non-allocated memory - av_copy_packet_side_data(&this_pktl->pkt, &this_pktl->pkt); // copy side data + if ((pkt->flags & AV_PKT_FLAG_UNCODED_FRAME)) { + av_assert0(pkt->size == UNCODED_FRAME_PACKET_SIZE); + av_assert0(((AVFrame *)pkt->data)->buf); + } else { + av_dup_packet(&this_pktl->pkt); // duplicate the packet if it uses non-allocated memory + av_copy_packet_side_data(&this_pktl->pkt, &this_pktl->pkt); // copy side data + } if (s->streams[pkt->stream_index]->last_in_packet_buffer) { next_point = &(st->last_in_packet_buffer->next); @@ -932,3 +955,51 @@ int ff_write_chained(AVFormatContext *dst, int dst_stream, AVPacket *pkt, dst->streams[dst_stream]->time_base); return av_write_frame(dst, &local_pkt); } + +static int av_write_uncoded_frame_internal(AVFormatContext *s, int stream_index, + AVFrame *frame, int interleaved) +{ + AVPacket pkt, *pktp; + + av_assert0(s->oformat); + if (!s->oformat->write_uncoded_frame) + return AVERROR(ENOSYS); + + if (!frame) { + pktp = NULL; + } else { + pktp = &pkt; + av_init_packet(&pkt); + pkt.data = (void *)frame; + pkt.size = UNCODED_FRAME_PACKET_SIZE; + pkt.pts = + pkt.dts = frame->pts; + pkt.duration = av_frame_get_pkt_duration(frame); + pkt.stream_index = stream_index; + pkt.flags |= AV_PKT_FLAG_UNCODED_FRAME; + } + + return interleaved ? av_interleaved_write_frame(s, pktp) : + av_write_frame(s, pktp); +} + +int av_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame) +{ + return av_write_uncoded_frame_internal(s, stream_index, frame, 0); +} + +int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame) +{ + return av_write_uncoded_frame_internal(s, stream_index, frame, 1); +} + +int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index) +{ + av_assert0(s->oformat); + if (!s->oformat->write_uncoded_frame) + return AVERROR(ENOSYS); + return s->oformat->write_uncoded_frame(s, stream_index, NULL, + AV_WRITE_UNCODED_FRAME_QUERY); +} diff --git a/libavformat/uncodedframecrcenc.c b/libavformat/uncodedframecrcenc.c new file mode 100644 index 0000000000..414683fe23 --- /dev/null +++ b/libavformat/uncodedframecrcenc.c @@ -0,0 +1,172 @@ +/* +* Copyright (c) 2013 Nicolas George +* +* This file is part of FFmpeg. +* +* FFmpeg is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public License +* as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* FFmpeg is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with FFmpeg; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "libavutil/adler32.h" +#include "libavutil/avassert.h" +#include "libavutil/bprint.h" +#include "libavutil/imgutils.h" +#include "libavutil/pixdesc.h" +#include "avformat.h" +#include "internal.h" + +/* Identical to Adler32 when the type is uint8_t. */ +#define DEFINE_CKSUM_LINE(name, type, conv) \ +static void cksum_line_ ## name(unsigned *cksum, void *data, unsigned size) \ +{ \ + type *p = data; \ + unsigned a = *cksum & 0xFFFF, b = *cksum >> 16; \ + for (; size > 0; size--, p++) { \ + a = (a + (unsigned)(conv)) % 65521; \ + b = (b + a) % 65521; \ + } \ + *cksum = a | (b << 16); \ +} + +DEFINE_CKSUM_LINE(u8, uint8_t, *p) +DEFINE_CKSUM_LINE(s16, int16_t, *p + 0x8000) +DEFINE_CKSUM_LINE(s32, int32_t, *p + 0x80000000) +DEFINE_CKSUM_LINE(flt, float, *p * 0x80000000 + 0x80000000) +DEFINE_CKSUM_LINE(dbl, double, *p * 0x80000000 + 0x80000000) + +static void video_frame_cksum(AVBPrint *bp, AVFrame *frame) +{ + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + int i, y; + uint8_t *data; + int linesize[5] = { 0 }; + + av_bprintf(bp, ", %d x %d", frame->width, frame->height); + if (!desc) { + av_bprintf(bp, ", unknown"); + return; + } + if (av_image_fill_linesizes(linesize, frame->format, frame->width) < 0) + return; + av_bprintf(bp, ", %s", desc->name); + for (i = 0; linesize[i]; i++) { + unsigned cksum = 0; + int h = frame->height; + if ((i == 1 || i == 2) && desc->nb_components >= 3) + h = -((-h) >> desc->log2_chroma_h); + data = frame->data[i]; + for (y = 0; y < h; y++) { + cksum = av_adler32_update(cksum, data, linesize[i]); + data += frame->linesize[i]; + } + av_bprintf(bp, ", 0x%08x", cksum); + } +} + +static void audio_frame_cksum(AVBPrint *bp, AVFrame *frame) +{ + int nb_planes, nb_samples, p; + const char *name; + + nb_planes = av_frame_get_channels(frame); + nb_samples = frame->nb_samples; + if (!av_sample_fmt_is_planar(frame->format)) { + nb_samples *= nb_planes; + nb_planes = 1; + } + name = av_get_sample_fmt_name(frame->format); + av_bprintf(bp, ", %d samples", frame->nb_samples); + av_bprintf(bp, ", %s", name ? name : "unknown"); + for (p = 0; p < nb_planes; p++) { + uint32_t cksum = 0; + void *d = frame->extended_data[p]; + switch (frame->format) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + cksum_line_u8(&cksum, d, nb_samples); + break; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + cksum_line_s16(&cksum, d, nb_samples); + break; + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + cksum_line_s32(&cksum, d, nb_samples); + break; + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_FLTP: + cksum_line_flt(&cksum, d, nb_samples); + break; + case AV_SAMPLE_FMT_DBL: + case AV_SAMPLE_FMT_DBLP: + cksum_line_dbl(&cksum, d, nb_samples); + break; + default: + av_assert0(!"reached"); + } + av_bprintf(bp, ", 0x%08x", cksum); + } +} + +static int write_frame(struct AVFormatContext *s, int stream_index, + AVFrame **frame, unsigned flags) +{ + AVBPrint bp; + int ret = 0; + enum AVMediaType type; + const char *type_name; + + if ((flags & AV_WRITE_UNCODED_FRAME_QUERY)) + return 0; + + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprintf(&bp, "%d, %10"PRId64"", + stream_index, (*frame)->pts); + type = s->streams[stream_index]->codec->codec_type; + type_name = av_get_media_type_string(type); + av_bprintf(&bp, ", %s", type_name ? type_name : "unknown"); + switch (type) { + case AVMEDIA_TYPE_VIDEO: + video_frame_cksum(&bp, *frame); + break; + case AVMEDIA_TYPE_AUDIO: + audio_frame_cksum(&bp, *frame); + break; + } + + av_bprint_chars(&bp, '\n', 1); + if (av_bprint_is_complete(&bp)) + avio_write(s->pb, bp.str, bp.len); + else + ret = AVERROR(ENOMEM); + av_bprint_finalize(&bp, NULL); + return ret; +} + +static int write_packet(struct AVFormatContext *s, AVPacket *pkt) +{ + return AVERROR(ENOSYS); +} + +AVOutputFormat ff_uncodedframecrc_muxer = { + .name = "uncodedframecrc", + .long_name = NULL_IF_CONFIG_SMALL("uncoded framecrc testing"), + .audio_codec = AV_CODEC_ID_PCM_S16LE, + .video_codec = AV_CODEC_ID_RAWVIDEO, + .write_header = ff_framehash_write_header, + .write_packet = write_packet, + .write_uncoded_frame = write_frame, + .flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | + AVFMT_TS_NEGATIVE, +}; diff --git a/libavformat/version.h b/libavformat/version.h index 440e9ca89b..e2d097cc47 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFORMAT_VERSION_MAJOR 55 -#define LIBAVFORMAT_VERSION_MINOR 31 +#define LIBAVFORMAT_VERSION_MINOR 32 #define LIBAVFORMAT_VERSION_MICRO 100 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ diff --git a/tools/uncoded_frame.c b/tools/uncoded_frame.c new file mode 100644 index 0000000000..da04d06ea1 --- /dev/null +++ b/tools/uncoded_frame.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include "libavutil/avassert.h" +#include "libavdevice/avdevice.h" +#include "libavfilter/avfilter.h" +#include "libavfilter/buffersink.h" +#include "libavformat/avformat.h" + +typedef struct { + AVFormatContext *mux; + AVStream *stream; + AVFilterContext *sink; + AVFilterLink *link; +} Stream; + +static int create_sink(Stream *st, AVFilterGraph *graph, + AVFilterContext *f, int idx) +{ + enum AVMediaType type = avfilter_pad_get_type(f->output_pads, idx); + const char *sink_name; + int ret; + + switch (type) { + case AVMEDIA_TYPE_VIDEO: sink_name = "buffersink"; break; + case AVMEDIA_TYPE_AUDIO: sink_name = "abuffersink"; break; + default: + av_log(NULL, AV_LOG_ERROR, "Stream type not supported\n"); + return AVERROR(EINVAL); + } + ret = avfilter_graph_create_filter(&st->sink, + avfilter_get_by_name(sink_name), + NULL, NULL, NULL, graph); + if (ret < 0) + return ret; + ret = avfilter_link(f, idx, st->sink, 0); + if (ret < 0) + return ret; + st->link = st->sink->inputs[0]; + return 0; +} + +int main(int argc, char **argv) +{ + char *in_graph_desc, **out_dev_name; + int nb_out_dev = 0, nb_streams = 0; + AVFilterGraph *in_graph = NULL; + Stream *streams = NULL, *st; + AVFrame *frame = NULL; + int i, j, run = 1, ret; + + //av_log_set_level(AV_LOG_DEBUG); + + if (argc < 3) { + av_log(NULL, AV_LOG_ERROR, + "Usage: %s filter_graph dev:out [dev2:out2...]\n\n" + "Examples:\n" + "%s movie=file.nut:s=v+a xv:- alsa:default\n" + "%s movie=file.nut:s=v+a uncodedframecrc:pipe:0\n", + argv[0], argv[0], argv[0]); + exit(1); + } + in_graph_desc = argv[1]; + out_dev_name = argv + 2; + nb_out_dev = argc - 2; + + av_register_all(); + avdevice_register_all(); + avfilter_register_all(); + + /* Create input graph */ + if (!(in_graph = avfilter_graph_alloc())) { + ret = AVERROR(ENOMEM); + av_log(NULL, AV_LOG_ERROR, "Unable to alloc graph graph: %s\n", + av_err2str(ret)); + goto fail; + } + ret = avfilter_graph_parse_ptr(in_graph, in_graph_desc, NULL, NULL, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Unable to parse graph: %s\n", + av_err2str(ret)); + goto fail; + } + nb_streams = 0; + for (i = 0; i < in_graph->nb_filters; i++) { + AVFilterContext *f = in_graph->filters[i]; + for (j = 0; j < f->nb_inputs; j++) { + if (!f->inputs[j]) { + av_log(NULL, AV_LOG_ERROR, "Graph has unconnected inputs\n"); + ret = AVERROR(EINVAL); + goto fail; + } + } + for (j = 0; j < f->nb_outputs; j++) + if (!f->outputs[j]) + nb_streams++; + } + if (!nb_streams) { + av_log(NULL, AV_LOG_ERROR, "Graph has no output stream\n"); + ret = AVERROR(EINVAL); + goto fail; + } + if (nb_out_dev != 1 && nb_out_dev != nb_streams) { + av_log(NULL, AV_LOG_ERROR, + "Graph has %d output streams, %d devices given\n", + nb_streams, nb_out_dev); + ret = AVERROR(EINVAL); + goto fail; + } + + if (!(streams = av_calloc(nb_streams, sizeof(*streams)))) { + ret = AVERROR(ENOMEM); + av_log(NULL, AV_LOG_ERROR, "Could not allocate streams\n"); + } + st = streams; + for (i = 0; i < in_graph->nb_filters; i++) { + AVFilterContext *f = in_graph->filters[i]; + for (j = 0; j < f->nb_outputs; j++) { + if (!f->outputs[j]) { + if ((ret = create_sink(st++, in_graph, f, j)) < 0) + goto fail; + } + } + } + av_assert0(st - streams == nb_streams); + if ((ret = avfilter_graph_config(in_graph, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to configure graph\n"); + goto fail; + } + + /* Create output devices */ + for (i = 0; i < nb_out_dev; i++) { + char *fmt = NULL, *dev = out_dev_name[i]; + st = &streams[i]; + if ((dev = strchr(dev, ':'))) { + *(dev++) = 0; + fmt = out_dev_name[i]; + } + ret = avformat_alloc_output_context2(&st->mux, NULL, fmt, dev); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to allocate output: %s\n", + av_err2str(ret)); + goto fail; + } + if (!(st->mux->oformat->flags & AVFMT_NOFILE)) { + ret = avio_open2(&st->mux->pb, st->mux->filename, AVIO_FLAG_WRITE, + NULL, NULL); + if (ret < 0) { + av_log(st->mux, AV_LOG_ERROR, "Failed to init output: %s\n", + av_err2str(ret)); + goto fail; + } + } + } + for (; i < nb_streams; i++) + streams[i].mux = streams[0].mux; + + /* Create output device streams */ + for (i = 0; i < nb_streams; i++) { + st = &streams[i]; + if (!(st->stream = avformat_new_stream(st->mux, NULL))) { + ret = AVERROR(ENOMEM); + av_log(NULL, AV_LOG_ERROR, "Failed to create output stream\n"); + goto fail; + } + st->stream->codec->codec_type = st->link->type; + st->stream->time_base = st->stream->codec->time_base = + st->link->time_base; + switch (st->link->type) { + case AVMEDIA_TYPE_VIDEO: + st->stream->codec->codec_id = AV_CODEC_ID_RAWVIDEO; + st->stream->avg_frame_rate = + st->stream-> r_frame_rate = av_buffersink_get_frame_rate(st->sink); + st->stream->codec->width = st->link->w; + st->stream->codec->height = st->link->h; + st->stream->codec->sample_aspect_ratio = st->link->sample_aspect_ratio; + st->stream->codec->pix_fmt = st->link->format; + break; + case AVMEDIA_TYPE_AUDIO: + st->stream->codec->codec_id = + av_get_pcm_codec(st->stream->codec->sample_fmt, -1); + st->stream->codec->channel_layout = st->link->channel_layout; + st->stream->codec->channels = avfilter_link_get_channels(st->link); + st->stream->codec->sample_rate = st->link->sample_rate; + st->stream->codec->sample_fmt = st->link->format; + break; + default: + av_assert0(!"reached"); + } + } + + /* Init output devices */ + for (i = 0; i < nb_out_dev; i++) { + st = &streams[i]; + if ((ret = avformat_write_header(st->mux, NULL)) < 0) { + av_log(st->mux, AV_LOG_ERROR, "Failed to init output: %s\n", + av_err2str(ret)); + goto fail; + } + } + + /* Check output devices */ + for (i = 0; i < nb_streams; i++) { + st = &streams[i]; + ret = av_write_uncoded_frame_query(st->mux, st->stream->index); + if (ret < 0) { + av_log(st->mux, AV_LOG_ERROR, + "Uncoded frames not supported on stream #%d: %s\n", + i, av_err2str(ret)); + goto fail; + } + } + + while (run) { + ret = avfilter_graph_request_oldest(in_graph); + if (ret < 0) { + if (ret == AVERROR_EOF) { + run = 0; + } else { + av_log(NULL, AV_LOG_ERROR, "Error filtering: %s\n", + av_err2str(ret)); + break; + } + } + for (i = 0; i < nb_streams; i++) { + st = &streams[i]; + while (1) { + if (!frame && !(frame = av_frame_alloc())) { + ret = AVERROR(ENOMEM); + av_log(NULL, AV_LOG_ERROR, "Could not allocate frame\n"); + goto fail; + } + ret = av_buffersink_get_frame_flags(st->sink, frame, + AV_BUFFERSINK_FLAG_NO_REQUEST); + if (ret < 0) { + if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) + av_log(NULL, AV_LOG_WARNING, "Error in sink: %s\n", + av_err2str(ret)); + break; + } + if (frame->pts != AV_NOPTS_VALUE) + frame->pts = av_rescale_q(frame->pts, + st->link ->time_base, + st->stream->time_base); + ret = av_interleaved_write_uncoded_frame(st->mux, + st->stream->index, + frame); + if (ret < 0) { + av_log(st->stream->codec, AV_LOG_ERROR, + "Error writing frame: %s\n", av_err2str(ret)); + goto fail; + } + frame = NULL; + } + } + } + ret = 0; + + for (i = 0; i < nb_out_dev; i++) { + st = &streams[i]; + av_write_trailer(st->mux); + } + +fail: + av_frame_free(&frame); + avfilter_graph_free(&in_graph); + if (streams) { + for (i = 0; i < nb_out_dev; i++) { + st = &streams[i]; + if (st->mux) { + if (st->mux->pb) + avio_close(st->mux->pb); + avformat_free_context(st->mux); + } + } + } + av_freep(&streams); + return ret < 0; +}