diff options
author | Anas Nashif <anas.nashif@intel.com> | 2012-11-04 07:15:40 -0800 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2012-11-04 07:15:40 -0800 |
commit | 20e158548a02a6ac87e5e662d790e88882e0e4d9 (patch) | |
tree | 9dc5064254d717cb8e242a1b68e4d8d77b04e84d /examples | |
download | libtheora-20e158548a02a6ac87e5e662d790e88882e0e4d9.tar.gz libtheora-20e158548a02a6ac87e5e662d790e88882e0e4d9.tar.bz2 libtheora-20e158548a02a6ac87e5e662d790e88882e0e4d9.zip |
Imported Upstream version 1.1.1upstream/1.1.1upstream
Diffstat (limited to 'examples')
-rw-r--r-- | examples/Makefile.am | 41 | ||||
-rw-r--r-- | examples/Makefile.in | 580 | ||||
-rw-r--r-- | examples/dump_psnr.c | 1201 | ||||
-rw-r--r-- | examples/dump_video.c | 496 | ||||
-rw-r--r-- | examples/encoder_example.c | 1826 | ||||
-rw-r--r-- | examples/getopt.c | 1055 | ||||
-rw-r--r-- | examples/getopt.h | 180 | ||||
-rw-r--r-- | examples/getopt1.c | 188 | ||||
-rw-r--r-- | examples/player_example.c | 857 | ||||
-rw-r--r-- | examples/png2theora.c | 912 |
10 files changed, 7336 insertions, 0 deletions
diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..fe66a21 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,41 @@ +## Process this file with automake to produce Makefile.in + +INCLUDES = -I$(top_srcdir)/include + +noinst_PROGRAMS = dump_video dump_psnr $(BUILDABLE_EXAMPLES) + +# possible contents of BUILDABLE_EXAMPLES: +EXTRA_PROGRAMS = player_example encoder_example png2theora + +AM_CFLAGS = $(OGG_CFLAGS) +LDADD = ../lib/libtheora.la $(OGG_LIBS) +LDADDDEC = ../lib/libtheoradec.la $(OGG_LIBS) +LDADDENC = ../lib/libtheoraenc.la ../lib/libtheoradec.la $(OGG_LIBS) + +dump_video_SOURCES = dump_video.c +EXTRA_dump_video_SOURCES = getopt.c getopt1.c getopt.h +dump_video_LDADD = $(GETOPT_OBJS) $(LDADDDEC) + +dump_psnr_SOURCES = dump_psnr.c +EXTRA_dump_psnr_SOURCES = getopt.c getopt1.c getopt.h +dump_psnr_LDADD = $(GETOPT_OBJS) $(LDADDDEC) -lm + +player_example_SOURCES = player_example.c +player_example_CFLAGS = $(SDL_CFLAGS) $(OGG_CFLAGS) $(VORBIS_CFLAGS) +player_example_LDADD = $(LDADDDEC) $(SDL_LIBS) $(VORBIS_LIBS) $(OSS_LIBS) + +encoder_example_SOURCES = encoder_example.c +EXTRA_encoder_example_SOURCES = getopt.c getopt1.c getopt.h +encoder_example_CFLAGS = $(OGG_CFLAGS) $(VORBIS_CFLAGS) +encoder_example_LDADD = $(GETOPT_OBJS) $(LDADDENC) $(VORBIS_LIBS) $(VORBISENC_LIBS) -lm + +png2theora_SOURCES = png2theora.c +png2theora_CFLAGS = $(OGG_CFLAGS) $(PNG_CFLAGS) +png2theora_LDADD = $(GETOPT_OBJS) $(LDADDENC) $(PNG_LIBS) -lm + +debug: + $(MAKE) all CFLAGS="@DEBUG@" + +profile: + $(MAKE) all CFLAGS="@PROFILE@" + diff --git a/examples/Makefile.in b/examples/Makefile.in new file mode 100644 index 0000000..64e6741 --- /dev/null +++ b/examples/Makefile.in @@ -0,0 +1,580 @@ +# Makefile.in generated by automake 1.6.3 from Makefile.am. +# @configure_input@ + +# Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 +# Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +SHELL = @SHELL@ + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +bindir = @bindir@ +sbindir = @sbindir@ +libexecdir = @libexecdir@ +datadir = @datadir@ +sysconfdir = @sysconfdir@ +sharedstatedir = @sharedstatedir@ +localstatedir = @localstatedir@ +libdir = @libdir@ +infodir = @infodir@ +mandir = @mandir@ +includedir = @includedir@ +oldincludedir = /usr/include +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = .. + +ACLOCAL = @ACLOCAL@ +AUTOCONF = @AUTOCONF@ +AUTOMAKE = @AUTOMAKE@ +AUTOHEADER = @AUTOHEADER@ + +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_HEADER = $(INSTALL_DATA) +transform = @program_transform_name@ +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +host_alias = @host_alias@ +host_triplet = @host@ + +EXEEXT = @EXEEXT@ +OBJEXT = @OBJEXT@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AR = @AR@ +ARGZ_H = @ARGZ_H@ +AS = @AS@ +AWK = @AWK@ +BUILDABLE_EXAMPLES = @BUILDABLE_EXAMPLES@ +CAIRO_CFLAGS = @CAIRO_CFLAGS@ +CAIRO_LIBS = @CAIRO_LIBS@ +CC = @CC@ +CPP = @CPP@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +DEBUG = @DEBUG@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +F77 = @F77@ +GCJ = @GCJ@ +GCJFLAGS = @GCJFLAGS@ +GETOPT_OBJS = @GETOPT_OBJS@ +GREP = @GREP@ +HAVE_BIBTEX = @HAVE_BIBTEX@ +HAVE_DOXYGEN = @HAVE_DOXYGEN@ +HAVE_PDFLATEX = @HAVE_PDFLATEX@ +HAVE_PKG_CONFIG = @HAVE_PKG_CONFIG@ +HAVE_TRANSFIG = @HAVE_TRANSFIG@ +HAVE_VALGRIND = @HAVE_VALGRIND@ +INCLTDL = @INCLTDL@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LIBADD_DL = @LIBADD_DL@ +LIBADD_DLD_LINK = @LIBADD_DLD_LINK@ +LIBADD_DLOPEN = @LIBADD_DLOPEN@ +LIBADD_SHL_LOAD = @LIBADD_SHL_LOAD@ +LIBLTDL = @LIBLTDL@ +LIBM = @LIBM@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTDLDEPS = @LTDLDEPS@ +LTDLINCL = @LTDLINCL@ +LTDLOPEN = @LTDLOPEN@ +LT_CONFIG_H = @LT_CONFIG_H@ +LT_DLLOADERS = @LT_DLLOADERS@ +LT_DLPREOPEN = @LT_DLPREOPEN@ +MAINT = @MAINT@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OGG_CFLAGS = @OGG_CFLAGS@ +OGG_LIBS = @OGG_LIBS@ +OSS_LIBS = @OSS_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PKG_CONFIG = @PKG_CONFIG@ +PNG_CFLAGS = @PNG_CFLAGS@ +PNG_LIBS = @PNG_LIBS@ +PROFILE = @PROFILE@ +RANLIB = @RANLIB@ +RC = @RC@ +SDL_CFLAGS = @SDL_CFLAGS@ +SDL_CONFIG = @SDL_CONFIG@ +SDL_LIBS = @SDL_LIBS@ +SED = @SED@ +STRIP = @STRIP@ +THDEC_LIB_AGE = @THDEC_LIB_AGE@ +THDEC_LIB_CURRENT = @THDEC_LIB_CURRENT@ +THDEC_LIB_REVISION = @THDEC_LIB_REVISION@ +THENC_LIB_AGE = @THENC_LIB_AGE@ +THENC_LIB_CURRENT = @THENC_LIB_CURRENT@ +THENC_LIB_REVISION = @THENC_LIB_REVISION@ +THEORADEC_LDFLAGS = @THEORADEC_LDFLAGS@ +THEORAENC_LDFLAGS = @THEORAENC_LDFLAGS@ +THEORA_LDFLAGS = @THEORA_LDFLAGS@ +TH_LIB_AGE = @TH_LIB_AGE@ +TH_LIB_CURRENT = @TH_LIB_CURRENT@ +TH_LIB_REVISION = @TH_LIB_REVISION@ +VALGRIND_ENVIRONMENT = @VALGRIND_ENVIRONMENT@ +VERSION = @VERSION@ +VORBISENC_LIBS = @VORBISENC_LIBS@ +VORBISFILE_LIBS = @VORBISFILE_LIBS@ +VORBIS_CFLAGS = @VORBIS_CFLAGS@ +VORBIS_LIBS = @VORBIS_LIBS@ +am__include = @am__include@ +am__quote = @am__quote@ +install_sh = @install_sh@ +lt_ECHO = @lt_ECHO@ +ltdl_LIBOBJS = @ltdl_LIBOBJS@ +ltdl_LTLIBOBJS = @ltdl_LTLIBOBJS@ +sys_symbol_underscore = @sys_symbol_underscore@ + +INCLUDES = -I$(top_srcdir)/include + +noinst_PROGRAMS = dump_video dump_psnr $(BUILDABLE_EXAMPLES) + +# possible contents of BUILDABLE_EXAMPLES: +EXTRA_PROGRAMS = player_example encoder_example png2theora + +AM_CFLAGS = $(OGG_CFLAGS) +LDADD = ../lib/libtheora.la $(OGG_LIBS) +LDADDDEC = ../lib/libtheoradec.la $(OGG_LIBS) +LDADDENC = ../lib/libtheoraenc.la ../lib/libtheoradec.la $(OGG_LIBS) + +dump_video_SOURCES = dump_video.c +EXTRA_dump_video_SOURCES = getopt.c getopt1.c getopt.h +dump_video_LDADD = $(GETOPT_OBJS) $(LDADDDEC) + +dump_psnr_SOURCES = dump_psnr.c +EXTRA_dump_psnr_SOURCES = getopt.c getopt1.c getopt.h +dump_psnr_LDADD = $(GETOPT_OBJS) $(LDADDDEC) -lm + +player_example_SOURCES = player_example.c +player_example_CFLAGS = $(SDL_CFLAGS) $(OGG_CFLAGS) $(VORBIS_CFLAGS) +player_example_LDADD = $(LDADDDEC) $(SDL_LIBS) $(VORBIS_LIBS) $(OSS_LIBS) + +encoder_example_SOURCES = encoder_example.c +EXTRA_encoder_example_SOURCES = getopt.c getopt1.c getopt.h +encoder_example_CFLAGS = $(OGG_CFLAGS) $(VORBIS_CFLAGS) +encoder_example_LDADD = $(GETOPT_OBJS) $(LDADDENC) $(VORBIS_LIBS) $(VORBISENC_LIBS) -lm + +png2theora_SOURCES = png2theora.c +png2theora_CFLAGS = $(OGG_CFLAGS) $(PNG_CFLAGS) +png2theora_LDADD = $(GETOPT_OBJS) $(LDADDENC) $(PNG_LIBS) -lm +subdir = examples +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +EXTRA_PROGRAMS = player_example$(EXEEXT) encoder_example$(EXEEXT) \ + png2theora$(EXEEXT) +noinst_PROGRAMS = dump_video$(EXEEXT) dump_psnr$(EXEEXT) \ + @BUILDABLE_EXAMPLES@ +PROGRAMS = $(noinst_PROGRAMS) + +am_dump_psnr_OBJECTS = dump_psnr.$(OBJEXT) +dump_psnr_OBJECTS = $(am_dump_psnr_OBJECTS) +dump_psnr_DEPENDENCIES = ../lib/libtheoradec.la +dump_psnr_LDFLAGS = +am_dump_video_OBJECTS = dump_video.$(OBJEXT) +dump_video_OBJECTS = $(am_dump_video_OBJECTS) +dump_video_DEPENDENCIES = ../lib/libtheoradec.la +dump_video_LDFLAGS = +am_encoder_example_OBJECTS = encoder_example-encoder_example.$(OBJEXT) +encoder_example_OBJECTS = $(am_encoder_example_OBJECTS) +encoder_example_DEPENDENCIES = ../lib/libtheoraenc.la \ + ../lib/libtheoradec.la +encoder_example_LDFLAGS = +am_player_example_OBJECTS = player_example-player_example.$(OBJEXT) +player_example_OBJECTS = $(am_player_example_OBJECTS) +player_example_DEPENDENCIES = ../lib/libtheoradec.la +player_example_LDFLAGS = +am_png2theora_OBJECTS = png2theora-png2theora.$(OBJEXT) +png2theora_OBJECTS = $(am_png2theora_OBJECTS) +png2theora_DEPENDENCIES = ../lib/libtheoraenc.la ../lib/libtheoradec.la +png2theora_LDFLAGS = + +DEFS = @DEFS@ +DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) +CPPFLAGS = @CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +@AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/dump_psnr.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/dump_video.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/encoder_example-encoder_example.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/encoder_example-getopt.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/encoder_example-getopt1.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/getopt.Po ./$(DEPDIR)/getopt1.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/player_example-player_example.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/png2theora-png2theora.Po +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) \ + $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +CFLAGS = @CFLAGS@ +DIST_SOURCES = $(dump_psnr_SOURCES) $(EXTRA_dump_psnr_SOURCES) \ + $(dump_video_SOURCES) $(EXTRA_dump_video_SOURCES) \ + $(encoder_example_SOURCES) $(EXTRA_encoder_example_SOURCES) \ + $(player_example_SOURCES) $(png2theora_SOURCES) +DIST_COMMON = Makefile.am Makefile.in +SOURCES = $(dump_psnr_SOURCES) $(EXTRA_dump_psnr_SOURCES) $(dump_video_SOURCES) $(EXTRA_dump_video_SOURCES) $(encoder_example_SOURCES) $(EXTRA_encoder_example_SOURCES) $(player_example_SOURCES) $(png2theora_SOURCES) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ Makefile.am $(top_srcdir)/configure.ac $(ACLOCAL_M4) + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu examples/Makefile +Makefile: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.in $(top_builddir)/config.status + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe) + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; for p in $$list; do \ + f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f $$p $$f"; \ + rm -f $$p $$f ; \ + done +dump_psnr$(EXEEXT): $(dump_psnr_OBJECTS) $(dump_psnr_DEPENDENCIES) + @rm -f dump_psnr$(EXEEXT) + $(LINK) $(dump_psnr_LDFLAGS) $(dump_psnr_OBJECTS) $(dump_psnr_LDADD) $(LIBS) +dump_video$(EXEEXT): $(dump_video_OBJECTS) $(dump_video_DEPENDENCIES) + @rm -f dump_video$(EXEEXT) + $(LINK) $(dump_video_LDFLAGS) $(dump_video_OBJECTS) $(dump_video_LDADD) $(LIBS) +encoder_example-encoder_example.$(OBJEXT): encoder_example.c +encoder_example-getopt.$(OBJEXT): getopt.c +encoder_example-getopt1.$(OBJEXT): getopt1.c +encoder_example$(EXEEXT): $(encoder_example_OBJECTS) $(encoder_example_DEPENDENCIES) + @rm -f encoder_example$(EXEEXT) + $(LINK) $(encoder_example_LDFLAGS) $(encoder_example_OBJECTS) $(encoder_example_LDADD) $(LIBS) +player_example-player_example.$(OBJEXT): player_example.c +player_example$(EXEEXT): $(player_example_OBJECTS) $(player_example_DEPENDENCIES) + @rm -f player_example$(EXEEXT) + $(LINK) $(player_example_LDFLAGS) $(player_example_OBJECTS) $(player_example_LDADD) $(LIBS) +png2theora-png2theora.$(OBJEXT): png2theora.c +png2theora$(EXEEXT): $(png2theora_OBJECTS) $(png2theora_DEPENDENCIES) + @rm -f png2theora$(EXEEXT) + $(LINK) $(png2theora_LDFLAGS) $(png2theora_OBJECTS) $(png2theora_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) core *.core + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump_psnr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump_video.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encoder_example-encoder_example.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encoder_example-getopt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encoder_example-getopt1.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt1.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/player_example-player_example.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/png2theora-png2theora.Po@am__quote@ + +distclean-depend: + -rm -rf ./$(DEPDIR) + +.c.o: +@AMDEP_TRUE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(COMPILE) -c `test -f '$<' || echo '$(srcdir)/'`$< + +.c.obj: +@AMDEP_TRUE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(COMPILE) -c `cygpath -w $<` + +.c.lo: +@AMDEP_TRUE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/$*.Plo' tmpdepfile='$(DEPDIR)/$*.TPlo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(LTCOMPILE) -c -o $@ `test -f '$<' || echo '$(srcdir)/'`$< + +encoder_example-encoder_example.o: encoder_example.c +@AMDEP_TRUE@ source='encoder_example.c' object='encoder_example-encoder_example.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-encoder_example.Po' tmpdepfile='$(DEPDIR)/encoder_example-encoder_example.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-encoder_example.o `test -f 'encoder_example.c' || echo '$(srcdir)/'`encoder_example.c + +encoder_example-encoder_example.obj: encoder_example.c +@AMDEP_TRUE@ source='encoder_example.c' object='encoder_example-encoder_example.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-encoder_example.Po' tmpdepfile='$(DEPDIR)/encoder_example-encoder_example.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-encoder_example.obj `cygpath -w encoder_example.c` + +encoder_example-encoder_example.lo: encoder_example.c +@AMDEP_TRUE@ source='encoder_example.c' object='encoder_example-encoder_example.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-encoder_example.Plo' tmpdepfile='$(DEPDIR)/encoder_example-encoder_example.TPlo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-encoder_example.lo `test -f 'encoder_example.c' || echo '$(srcdir)/'`encoder_example.c + +encoder_example-getopt.o: getopt.c +@AMDEP_TRUE@ source='getopt.c' object='encoder_example-getopt.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-getopt.Po' tmpdepfile='$(DEPDIR)/encoder_example-getopt.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-getopt.o `test -f 'getopt.c' || echo '$(srcdir)/'`getopt.c + +encoder_example-getopt.obj: getopt.c +@AMDEP_TRUE@ source='getopt.c' object='encoder_example-getopt.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-getopt.Po' tmpdepfile='$(DEPDIR)/encoder_example-getopt.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-getopt.obj `cygpath -w getopt.c` + +encoder_example-getopt.lo: getopt.c +@AMDEP_TRUE@ source='getopt.c' object='encoder_example-getopt.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-getopt.Plo' tmpdepfile='$(DEPDIR)/encoder_example-getopt.TPlo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-getopt.lo `test -f 'getopt.c' || echo '$(srcdir)/'`getopt.c + +encoder_example-getopt1.o: getopt1.c +@AMDEP_TRUE@ source='getopt1.c' object='encoder_example-getopt1.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-getopt1.Po' tmpdepfile='$(DEPDIR)/encoder_example-getopt1.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-getopt1.o `test -f 'getopt1.c' || echo '$(srcdir)/'`getopt1.c + +encoder_example-getopt1.obj: getopt1.c +@AMDEP_TRUE@ source='getopt1.c' object='encoder_example-getopt1.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-getopt1.Po' tmpdepfile='$(DEPDIR)/encoder_example-getopt1.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-getopt1.obj `cygpath -w getopt1.c` + +encoder_example-getopt1.lo: getopt1.c +@AMDEP_TRUE@ source='getopt1.c' object='encoder_example-getopt1.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/encoder_example-getopt1.Plo' tmpdepfile='$(DEPDIR)/encoder_example-getopt1.TPlo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(encoder_example_CFLAGS) $(CFLAGS) -c -o encoder_example-getopt1.lo `test -f 'getopt1.c' || echo '$(srcdir)/'`getopt1.c + +player_example-player_example.o: player_example.c +@AMDEP_TRUE@ source='player_example.c' object='player_example-player_example.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/player_example-player_example.Po' tmpdepfile='$(DEPDIR)/player_example-player_example.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(player_example_CFLAGS) $(CFLAGS) -c -o player_example-player_example.o `test -f 'player_example.c' || echo '$(srcdir)/'`player_example.c + +player_example-player_example.obj: player_example.c +@AMDEP_TRUE@ source='player_example.c' object='player_example-player_example.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/player_example-player_example.Po' tmpdepfile='$(DEPDIR)/player_example-player_example.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(player_example_CFLAGS) $(CFLAGS) -c -o player_example-player_example.obj `cygpath -w player_example.c` + +player_example-player_example.lo: player_example.c +@AMDEP_TRUE@ source='player_example.c' object='player_example-player_example.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/player_example-player_example.Plo' tmpdepfile='$(DEPDIR)/player_example-player_example.TPlo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(player_example_CFLAGS) $(CFLAGS) -c -o player_example-player_example.lo `test -f 'player_example.c' || echo '$(srcdir)/'`player_example.c + +png2theora-png2theora.o: png2theora.c +@AMDEP_TRUE@ source='png2theora.c' object='png2theora-png2theora.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/png2theora-png2theora.Po' tmpdepfile='$(DEPDIR)/png2theora-png2theora.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(png2theora_CFLAGS) $(CFLAGS) -c -o png2theora-png2theora.o `test -f 'png2theora.c' || echo '$(srcdir)/'`png2theora.c + +png2theora-png2theora.obj: png2theora.c +@AMDEP_TRUE@ source='png2theora.c' object='png2theora-png2theora.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/png2theora-png2theora.Po' tmpdepfile='$(DEPDIR)/png2theora-png2theora.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(png2theora_CFLAGS) $(CFLAGS) -c -o png2theora-png2theora.obj `cygpath -w png2theora.c` + +png2theora-png2theora.lo: png2theora.c +@AMDEP_TRUE@ source='png2theora.c' object='png2theora-png2theora.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@ depfile='$(DEPDIR)/png2theora-png2theora.Plo' tmpdepfile='$(DEPDIR)/png2theora-png2theora.TPlo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ + $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(png2theora_CFLAGS) $(CFLAGS) -c -o png2theora-png2theora.lo `test -f 'png2theora.c' || echo '$(srcdir)/'`png2theora.c +CCDEPMODE = @CCDEPMODE@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: + +ETAGS = etags +ETAGSFLAGS = + +tags: TAGS + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(ETAGS_ARGS)$$tags$$unique" \ + || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) + +top_distdir = .. +distdir = $(top_distdir)/$(PACKAGE)-$(VERSION) + +distdir: $(DISTFILES) + @list='$(DISTFILES)'; for file in $$list; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkinstalldirs) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) + +installdirs: + +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -rm -f Makefile $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + +distclean-am: clean-am distclean-compile distclean-depend \ + distclean-generic distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +info: info-am + +info-am: + +install-data-am: + +install-exec-am: + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +uninstall-am: uninstall-info-am + +.PHONY: GTAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstPROGRAMS distclean distclean-compile \ + distclean-depend distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am info info-am install \ + install-am install-data install-data-am install-exec \ + install-exec-am install-info install-info-am install-man \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + tags uninstall uninstall-am uninstall-info-am + + +debug: + $(MAKE) all CFLAGS="@DEBUG@" + +profile: + $(MAKE) all CFLAGS="@PROFILE@" +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/examples/dump_psnr.c b/examples/dump_psnr.c new file mode 100644 index 0000000..7340cad --- /dev/null +++ b/examples/dump_psnr.c @@ -0,0 +1,1201 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009 * + * by the Xiph.Org Foundation and contributors http://www.xiph.org/ * + * * + ******************************************************************** + + function: example dumpvid application; dumps Theora streams + last mod: $Id: dump_video.c 15675 2009-02-06 09:43:27Z tterribe $ + + ********************************************************************/ + +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif +#if !defined(_LARGEFILE_SOURCE) +#define _LARGEFILE_SOURCE +#endif +#if !defined(_LARGEFILE64_SOURCE) +#define _LARGEFILE64_SOURCE +#endif +#if !defined(_FILE_OFFSET_BITS) +#define _FILE_OFFSET_BITS 64 +#endif + +#include <stdio.h> +#if !defined(_WIN32) +#include <getopt.h> +#include <unistd.h> +#else +#include "getopt.h" +#endif +#include <stdlib.h> +#include <string.h> +#include <sys/timeb.h> +#include <sys/types.h> +#include <sys/stat.h> +/*Yes, yes, we're going to hell.*/ +#if defined(_WIN32) +#include <io.h> +#endif +#include <fcntl.h> +#include <math.h> +#include <signal.h> +#include "theora/theoradec.h" + +const char *optstring = "fsy"; +struct option options [] = { + {"frame-type",no_argument,NULL,'f'}, + {"summary",no_argument,NULL,'s'}, + {"luma-only",no_argument,NULL,'y'}, + {NULL,0,NULL,0} +}; + +static int show_frame_type; +static int summary_only; +static int luma_only; + +typedef struct y4m_input y4m_input; + +/*The function used to perform chroma conversion.*/ +typedef void (*y4m_convert_func)(y4m_input *_y4m, + unsigned char *_dst,unsigned char *_aux); + +struct y4m_input{ + int frame_w; + int frame_h; + int pic_w; + int pic_h; + int pic_x; + int pic_y; + int fps_n; + int fps_d; + int par_n; + int par_d; + char interlace; + int src_c_dec_h; + int src_c_dec_v; + int dst_c_dec_h; + int dst_c_dec_v; + char chroma_type[16]; + /*The size of each converted frame buffer.*/ + size_t dst_buf_sz; + /*The amount to read directly into the converted frame buffer.*/ + size_t dst_buf_read_sz; + /*The size of the auxilliary buffer.*/ + size_t aux_buf_sz; + /*The amount to read into the auxilliary buffer.*/ + size_t aux_buf_read_sz; + y4m_convert_func convert; + unsigned char *dst_buf; + unsigned char *aux_buf; +}; + + +static int y4m_parse_tags(y4m_input *_y4m,char *_tags){ + int got_w; + int got_h; + int got_fps; + int got_interlace; + int got_par; + int got_chroma; + char *p; + char *q; + got_w=got_h=got_fps=got_interlace=got_par=got_chroma=0; + for(p=_tags;;p=q){ + /*Skip any leading spaces.*/ + while(*p==' ')p++; + /*If that's all we have, stop.*/ + if(p[0]=='\0')break; + /*Find the end of this tag.*/ + for(q=p+1;*q!='\0'&&*q!=' ';q++); + /*Process the tag.*/ + switch(p[0]){ + case 'W':{ + if(sscanf(p+1,"%d",&_y4m->pic_w)!=1)return -1; + got_w=1; + }break; + case 'H':{ + if(sscanf(p+1,"%d",&_y4m->pic_h)!=1)return -1; + got_h=1; + }break; + case 'F':{ + if(sscanf(p+1,"%d:%d",&_y4m->fps_n,&_y4m->fps_d)!=2){ + return -1; + } + got_fps=1; + }break; + case 'I':{ + _y4m->interlace=p[1]; + got_interlace=1; + }break; + case 'A':{ + if(sscanf(p+1,"%d:%d",&_y4m->par_n,&_y4m->par_d)!=2){ + return -1; + } + got_par=1; + }break; + case 'C':{ + if(q-p>16)return -1; + memcpy(_y4m->chroma_type,p+1,q-p-1); + _y4m->chroma_type[q-p-1]='\0'; + got_chroma=1; + }break; + /*Ignore unknown tags.*/ + } + } + if(!got_w||!got_h||!got_fps||!got_interlace||!got_par)return -1; + /*Chroma-type is not specified in older files, e.g., those generated by + mplayer.*/ + if(!got_chroma)strcpy(_y4m->chroma_type,"420"); + return 0; +} + +/*All anti-aliasing filters in the following conversion functions are based on + one of two window functions: + The 6-tap Lanczos window (for down-sampling and shifts): + sinc(\pi*t)*sinc(\pi*t/3), |t|<3 (sinc(t)==sin(t)/t) + 0, |t|>=3 + The 4-tap Mitchell window (for up-sampling): + 7|t|^3-12|t|^2+16/3, |t|<1 + -(7/3)|x|^3+12|x|^2-20|x|+32/3, |t|<2 + 0, |t|>=2 + The number of taps is intentionally kept small to reduce computational + overhead and limit ringing. + + The taps from these filters are scaled so that their sum is 1, and the result + is scaled by 128 and rounded to integers to create a filter whose + intermediate values fit inside 16 bits. + Coefficients are rounded in such a way as to ensure their sum is still 128, + which is usually equivalent to normal rounding.*/ + +#define OC_MINI(_a,_b) ((_a)>(_b)?(_b):(_a)) +#define OC_MAXI(_a,_b) ((_a)<(_b)?(_b):(_a)) +#define OC_CLAMPI(_a,_b,_c) (OC_MAXI(_a,OC_MINI(_b,_c))) + +/*420jpeg chroma samples are sited like: + Y-------Y-------Y-------Y------- + | | | | + | BR | | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | BR | | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + + 420mpeg2 chroma samples are sited like: + Y-------Y-------Y-------Y------- + | | | | + BR | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + Y-------Y-------Y-------Y------- + | | | | + BR | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + + We use a resampling filter to shift the site locations one quarter pixel (at + the chroma plane's resolution) to the right. + The 4:2:2 modes look exactly the same, except there are twice as many chroma + lines, and they are vertically co-sited with the luma samples in both the + mpeg2 and jpeg cases (thus requiring no vertical resampling).*/ +static void y4m_convert_42xmpeg2_42xjpeg(y4m_input *_y4m,unsigned char *_dst, + unsigned char *_aux){ + int c_w; + int c_h; + int pli; + int y; + int x; + /*Skip past the luma data.*/ + _dst+=_y4m->pic_w*_y4m->pic_h; + /*Compute the size of each chroma plane.*/ + c_w=(_y4m->pic_w+_y4m->dst_c_dec_h-1)/_y4m->dst_c_dec_h; + c_h=(_y4m->pic_h+_y4m->dst_c_dec_v-1)/_y4m->dst_c_dec_v; + for(pli=1;pli<3;pli++){ + for(y=0;y<c_h;y++){ + /*Filter: [4 -17 114 35 -9 1]/128, derived from a 6-tap Lanczos + window.*/ + for(x=0;x<OC_MINI(c_w,2);x++){ + _dst[x]=(unsigned char)OC_CLAMPI(0,4*_aux[0]-17*_aux[OC_MAXI(x-1,0)]+ + 114*_aux[x]+35*_aux[OC_MINI(x+1,c_w-1)]-9*_aux[OC_MINI(x+2,c_w-1)]+ + _aux[OC_MINI(x+3,c_w-1)]+64>>7,255); + } + for(;x<c_w-3;x++){ + _dst[x]=(unsigned char)OC_CLAMPI(0,4*_aux[x-2]-17*_aux[x-1]+ + 114*_aux[x]+35*_aux[x+1]-9*_aux[x+2]+_aux[x+3]+64>>7,255); + } + for(;x<c_w;x++){ + _dst[x]=(unsigned char)OC_CLAMPI(0,4*_aux[x-2]-17*_aux[x-1]+ + 114*_aux[x]+35*_aux[OC_MINI(x+1,c_w-1)]-9*_aux[OC_MINI(x+2,c_w-1)]+ + _aux[c_w-1]+64>>7,255); + } + _dst+=c_w; + _aux+=c_w; + } + } +} + +/*This format is only used for interlaced content, but is included for + completeness. + + 420jpeg chroma samples are sited like: + Y-------Y-------Y-------Y------- + | | | | + | BR | | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | BR | | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + + 420paldv chroma samples are sited like: + YR------Y-------YR------Y------- + | | | | + | | | | + | | | | + YB------Y-------YB------Y------- + | | | | + | | | | + | | | | + YR------Y-------YR------Y------- + | | | | + | | | | + | | | | + YB------Y-------YB------Y------- + | | | | + | | | | + | | | | + + We use a resampling filter to shift the site locations one quarter pixel (at + the chroma plane's resolution) to the right. + Then we use another filter to move the C_r location down one quarter pixel, + and the C_b location up one quarter pixel.*/ +static void y4m_convert_42xpaldv_42xjpeg(y4m_input *_y4m,unsigned char *_dst, + unsigned char *_aux){ + unsigned char *tmp; + int c_w; + int c_h; + int c_sz; + int pli; + int y; + int x; + /*Skip past the luma data.*/ + _dst+=_y4m->pic_w*_y4m->pic_h; + /*Compute the size of each chroma plane.*/ + c_w=(_y4m->pic_w+1)/2; + c_h=(_y4m->pic_h+_y4m->dst_c_dec_h-1)/_y4m->dst_c_dec_h; + c_sz=c_w*c_h; + /*First do the horizontal re-sampling. + This is the same as the mpeg2 case, except that after the horizontal case, + we need to apply a second vertical filter.*/ + tmp=_aux+2*c_sz; + for(pli=1;pli<3;pli++){ + for(y=0;y<c_h;y++){ + /*Filter: [4 -17 114 35 -9 1]/128, derived from a 6-tap Lanczos + window.*/ + for(x=0;x<OC_MINI(c_w,2);x++){ + tmp[x]=(unsigned char)OC_CLAMPI(0,4*_aux[0]-17*_aux[OC_MAXI(x-1,0)]+ + 114*_aux[x]+35*_aux[OC_MINI(x+1,c_w-1)]-9*_aux[OC_MINI(x+2,c_w-1)]+ + _aux[OC_MINI(x+3,c_w-1)]+64>>7,255); + } + for(;x<c_w-3;x++){ + tmp[x]=(unsigned char)OC_CLAMPI(0,4*_aux[x-2]-17*_aux[x-1]+ + 114*_aux[x]+35*_aux[x+1]-9*_aux[x+2]+_aux[x+3]+64>>7,255); + } + for(;x<c_w;x++){ + tmp[x]=(unsigned char)OC_CLAMPI(0,4*_aux[x-2]-17*_aux[x-1]+ + 114*_aux[x]+35*_aux[OC_MINI(x+1,c_w-1)]-9*_aux[OC_MINI(x+2,c_w-1)]+ + _aux[c_w-1]+64>>7,255); + } + tmp+=c_w; + _aux+=c_w; + } + switch(pli){ + case 1:{ + tmp-=c_sz; + /*Slide C_b up a quarter-pel. + This is the same filter used above, but in the other order.*/ + for(x=0;x<c_w;x++){ + for(y=0;y<OC_MINI(c_h,3);y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,tmp[0]- + 9*tmp[OC_MAXI(y-2,0)*c_w]+35*tmp[OC_MAXI(y-1,0)*c_w]+ + 114*tmp[y*c_w]-17*tmp[OC_MINI(y+1,c_h-1)*c_w]+ + 4*tmp[OC_MINI(y+2,c_h-1)*c_w]+64>>7,255); + } + for(;y<c_h-2;y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,tmp[(y-3)*c_w]- + 9*tmp[(y-2)*c_w]+35*tmp[(y-1)*c_w]+114*tmp[y*c_w]- + 17*tmp[(y+1)*c_w]+4*tmp[(y+2)*c_w]+64>>7,255); + } + for(;y<c_h;y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,tmp[(y-3)*c_w]- + 9*tmp[(y-2)*c_w]+35*tmp[(y-1)*c_w]+114*tmp[y*c_w]- + 17*tmp[OC_MINI(y+1,c_h-1)*c_w]+4*tmp[(c_h-1)*c_w]+64>>7,255); + } + _dst++; + tmp++; + } + _dst+=c_sz-c_w; + tmp-=c_w; + }break; + case 2:{ + tmp-=c_sz; + /*Slide C_r down a quarter-pel. + This is the same as the horizontal filter.*/ + for(x=0;x<c_w;x++){ + for(y=0;y<OC_MINI(c_h,2);y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,4*tmp[0]- + 17*tmp[OC_MAXI(y-1,0)*c_w]+114*tmp[y*c_w]+ + 35*tmp[OC_MINI(y+1,c_h-1)*c_w]-9*tmp[OC_MINI(y+2,c_h-1)*c_w]+ + tmp[OC_MINI(y+3,c_h-1)*c_w]+64>>7,255); + } + for(;y<c_h-3;y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,4*tmp[(y-2)*c_w]- + 17*tmp[(y-1)*c_w]+114*tmp[y*c_w]+35*tmp[(y+1)*c_w]- + 9*tmp[(y+2)*c_w]+tmp[(y+3)*c_w]+64>>7,255); + } + for(;y<c_h;y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,4*tmp[(y-2)*c_w]- + 17*tmp[(y-1)*c_w]+114*tmp[y*c_w]+35*tmp[OC_MINI(y+1,c_h-1)*c_w]- + 9*tmp[OC_MINI(y+2,c_h-1)*c_w]+tmp[(c_h-1)*c_w]+64>>7,255); + } + _dst++; + tmp++; + } + }break; + } + /*For actual interlaced material, this would have to be done separately on + each field, and the shift amounts would be different. + C_r moves down 1/8, C_b up 3/8 in the top field, and C_r moves down 3/8, + C_b up 1/8 in the bottom field. + The corresponding filters would be: + Down 1/8 (reverse order for up): [3 -11 125 15 -4 0]/128 + Down 3/8 (reverse order for up): [4 -19 98 56 -13 2]/128*/ + } +} + +/*422jpeg chroma samples are sited like: + Y---BR--Y-------Y---BR--Y------- + | | | | + | | | | + | | | | + Y---BR--Y-------Y---BR--Y------- + | | | | + | | | | + | | | | + Y---BR--Y-------Y---BR--Y------- + | | | | + | | | | + | | | | + Y---BR--Y-------Y---BR--Y------- + | | | | + | | | | + | | | | + + 411 chroma samples are sited like: + YBR-----Y-------Y-------Y------- + | | | | + | | | | + | | | | + YBR-----Y-------Y-------Y------- + | | | | + | | | | + | | | | + YBR-----Y-------Y-------Y------- + | | | | + | | | | + | | | | + YBR-----Y-------Y-------Y------- + | | | | + | | | | + | | | | + + We use a filter to resample at site locations one eighth pixel (at the source + chroma plane's horizontal resolution) and five eighths of a pixel to the + right.*/ +static void y4m_convert_411_422jpeg(y4m_input *_y4m,unsigned char *_dst, + unsigned char *_aux){ + int c_w; + int dst_c_w; + int c_h; + int pli; + int y; + int x; + /*Skip past the luma data.*/ + _dst+=_y4m->pic_w*_y4m->pic_h; + /*Compute the size of each chroma plane.*/ + c_w=(_y4m->pic_w+_y4m->src_c_dec_h-1)/_y4m->src_c_dec_h; + dst_c_w=(_y4m->pic_w+_y4m->dst_c_dec_h-1)/_y4m->dst_c_dec_h; + c_h=(_y4m->pic_h+_y4m->dst_c_dec_v-1)/_y4m->dst_c_dec_v; + for(pli=1;pli<3;pli++){ + for(y=0;y<c_h;y++){ + /*Filters: [1 110 18 -1]/128 and [-3 50 86 -5]/128, both derived from a + 4-tap Mitchell window.*/ + for(x=0;x<OC_MINI(c_w,1);x++){ + _dst[x<<1]=(unsigned char)OC_CLAMPI(0,111*_aux[0]+ + 18*_aux[OC_MINI(1,c_w-1)]-_aux[OC_MINI(2,c_w-1)]+64>>7,255); + _dst[x<<1|1]=(unsigned char)OC_CLAMPI(0,47*_aux[0]+ + 86*_aux[OC_MINI(1,c_w-1)]-5*_aux[OC_MINI(2,c_w-1)]+64>>7,255); + } + for(;x<c_w-2;x++){ + _dst[x<<1]=(unsigned char)OC_CLAMPI(0,_aux[x-1]+110*_aux[x]+ + 18*_aux[x+1]-_aux[x+2]+64>>7,255); + _dst[x<<1|1]=(unsigned char)OC_CLAMPI(0,-3*_aux[x-1]+50*_aux[x]+ + 86*_aux[x+1]-5*_aux[x+2]+64>>7,255); + } + for(;x<c_w;x++){ + _dst[x<<1]=(unsigned char)OC_CLAMPI(0,_aux[x-1]+110*_aux[x]+ + 18*_aux[OC_MINI(x+1,c_w-1)]-_aux[c_w-1]+64>>7,255); + if((x<<1|1)<dst_c_w){ + _dst[x<<1|1]=(unsigned char)OC_CLAMPI(0,-3*_aux[x-1]+50*_aux[x]+ + 86*_aux[OC_MINI(x+1,c_w-1)]-5*_aux[c_w-1]+64>>7,255); + } + } + _dst+=dst_c_w; + _aux+=c_w; + } + } +} + +/*The image is padded with empty chroma components at 4:2:0. + This costs about 17 bits a frame to code.*/ +static void y4m_convert_mono_420jpeg(y4m_input *_y4m,unsigned char *_dst, + unsigned char *_aux){ + int c_sz; + _dst+=_y4m->pic_w*_y4m->pic_h; + c_sz=((_y4m->pic_w+_y4m->dst_c_dec_h-1)/_y4m->dst_c_dec_h)* + ((_y4m->pic_h+_y4m->dst_c_dec_v-1)/_y4m->dst_c_dec_v); + memset(_dst,128,c_sz*2); +} + +#if 0 +/*Right now just 444 to 420. + Not too hard to generalize.*/ +static void y4m_convert_4xxjpeg_42xjpeg(y4m_input *_y4m,unsigned char *_dst, + unsigned char *_aux){ + unsigned char *tmp; + int c_w; + int c_h; + int pic_sz; + int tmp_sz; + int c_sz; + int pli; + int y; + int x; + /*Compute the size of each chroma plane.*/ + c_w=(_y4m->pic_w+_y4m->dst_c_dec_h-1)/_y4m->dst_c_dec_h; + c_h=(_y4m->pic_h+_y4m->dst_c_dec_v-1)/_y4m->dst_c_dec_v; + pic_sz=_y4m->pic_w*_y4m->pic_h; + tmp_sz=c_w*_y4m->pic_h; + c_sz=c_w*c_h; + _dst+=pic_sz; + for(pli=1;pli<3;pli++){ + tmp=_aux+pic_sz; + /*In reality, the horizontal and vertical steps could be pipelined, for + less memory consumption and better cache performance, but we do them + separately for simplicity.*/ + /*First do horizontal filtering (convert to 4:2:2)*/ + /*Filter: [3 -17 78 78 -17 3]/128, derived from a 6-tap Lanczos window.*/ + for(y=0;y<_y4m->pic_h;y++){ + for(x=0;x<OC_MINI(_y4m->pic_w,2);x+=2){ + tmp[x>>1]=OC_CLAMPI(0,64*_aux[0]+78*_aux[OC_MINI(1,_y4m->pic_w-1)] + -17*_aux[OC_MINI(2,_y4m->pic_w-1)] + +3*_aux[OC_MINI(3,_y4m->pic_w-1)]+64>>7,255); + } + for(;x<_y4m->pic_w-3;x+=2){ + tmp[x>>1]=OC_CLAMPI(0,3*(_aux[x-2]+_aux[x+3])-17*(_aux[x-1]+_aux[x+2])+ + 78*(_aux[x]+_aux[x+1])+64>>7,255); + } + for(;x<_y4m->pic_w;x+=2){ + tmp[x>>1]=OC_CLAMPI(0,3*(_aux[x-2]+_aux[_y4m->pic_w-1])- + 17*(_aux[x-1]+_aux[OC_MINI(x+2,_y4m->pic_w-1)])+ + 78*(_aux[x]+_aux[OC_MINI(x+1,_y4m->pic_w-1)])+64>>7,255); + } + tmp+=c_w; + _aux+=_y4m->pic_w; + } + _aux-=pic_sz; + tmp-=tmp_sz; + /*Now do the vertical filtering.*/ + for(x=0;x<c_w;x++){ + for(y=0;y<OC_MINI(_y4m->pic_h,2);y+=2){ + _dst[(y>>1)*c_w]=OC_CLAMPI(0,64*tmp[0] + +78*tmp[OC_MINI(1,_y4m->pic_h-1)*c_w] + -17*tmp[OC_MINI(2,_y4m->pic_h-1)*c_w] + +3*tmp[OC_MINI(3,_y4m->pic_h-1)*c_w]+64>>7,255); + } + for(;y<_y4m->pic_h-3;y+=2){ + _dst[(y>>1)*c_w]=OC_CLAMPI(0,3*(tmp[(y-2)*c_w]+tmp[(y+3)*c_w])- + 17*(tmp[(y-1)*c_w]+tmp[(y+2)*c_w])+78*(tmp[y*c_w]+tmp[(y+1)*c_w])+ + 64>>7,255); + } + for(;y<_y4m->pic_h;y+=2){ + _dst[(y>>1)*c_w]=OC_CLAMPI(0,3*(tmp[(y-2)*c_w] + +tmp[(_y4m->pic_h-1)*c_w])-17*(tmp[(y-1)*c_w] + +tmp[OC_MINI(y+2,_y4m->pic_h-1)*c_w]) + +78*(tmp[y*c_w]+tmp[OC_MINI(y+1,_y4m->pic_h-1)*c_w])+64>>7,255); + } + tmp++; + _dst++; + } + _dst-=c_w; + } +} +#endif + +/*No conversion function needed.*/ +static void y4m_convert_null(y4m_input *_y4m,unsigned char *_dst, + unsigned char *_aux){ +} + +static int y4m_input_open(y4m_input *_y4m,FILE *_fin,char *_skip,int _nskip){ + char buffer[80]; + int ret; + int i; + /*Read until newline, or 80 cols, whichever happens first.*/ + for(i=0;i<79;i++){ + if(_nskip>0){ + buffer[i]=*_skip++; + _nskip--; + } + else{ + ret=fread(buffer+i,1,1,_fin); + if(ret<1)return -1; + } + if(buffer[i]=='\n')break; + } + /*We skipped too much header data.*/ + if(_nskip>0)return -1; + if(i==79){ + fprintf(stderr,"Error parsing header; not a YUV2MPEG2 file?\n"); + return -1; + } + buffer[i]='\0'; + if(memcmp(buffer,"YUV4MPEG",8)){ + fprintf(stderr,"Incomplete magic for YUV4MPEG file.\n"); + return -1; + } + if(buffer[8]!='2'){ + fprintf(stderr,"Incorrect YUV input file version; YUV4MPEG2 required.\n"); + } + ret=y4m_parse_tags(_y4m,buffer+5); + if(ret<0){ + fprintf(stderr,"Error parsing YUV4MPEG2 header.\n"); + return ret; + } + if(_y4m->interlace!='p'){ + fprintf(stderr,"Input video is interlaced; " + "Theora only handles progressive scan.\n"); + return -1; + } + if(strcmp(_y4m->chroma_type,"420")==0|| + strcmp(_y4m->chroma_type,"420jpeg")==0){ + _y4m->src_c_dec_h=_y4m->dst_c_dec_h=_y4m->src_c_dec_v=_y4m->dst_c_dec_v=2; + _y4m->dst_buf_read_sz=_y4m->pic_w*_y4m->pic_h + +2*((_y4m->pic_w+1)/2)*((_y4m->pic_h+1)/2); + /*Natively supported: no conversion required.*/ + _y4m->aux_buf_sz=_y4m->aux_buf_read_sz=0; + _y4m->convert=y4m_convert_null; + } + else if(strcmp(_y4m->chroma_type,"420mpeg2")==0){ + _y4m->src_c_dec_h=_y4m->dst_c_dec_h=_y4m->src_c_dec_v=_y4m->dst_c_dec_v=2; + _y4m->dst_buf_read_sz=_y4m->pic_w*_y4m->pic_h; + /*Chroma filter required: read into the aux buf first.*/ + _y4m->aux_buf_sz=_y4m->aux_buf_read_sz= + 2*((_y4m->pic_w+1)/2)*((_y4m->pic_h+1)/2); + _y4m->convert=y4m_convert_42xmpeg2_42xjpeg; + } + else if(strcmp(_y4m->chroma_type,"420paldv")==0){ + _y4m->src_c_dec_h=_y4m->dst_c_dec_h=_y4m->src_c_dec_v=_y4m->dst_c_dec_v=2; + _y4m->dst_buf_read_sz=_y4m->pic_w*_y4m->pic_h; + /*Chroma filter required: read into the aux buf first. + We need to make two filter passes, so we need some extra space in the + aux buffer.*/ + _y4m->aux_buf_sz=3*((_y4m->pic_w+1)/2)*((_y4m->pic_h+1)/2); + _y4m->aux_buf_read_sz=2*((_y4m->pic_w+1)/2)*((_y4m->pic_h+1)/2); + _y4m->convert=y4m_convert_42xpaldv_42xjpeg; + } + else if(strcmp(_y4m->chroma_type,"422")==0){ + _y4m->src_c_dec_h=_y4m->dst_c_dec_h=2; + _y4m->src_c_dec_v=_y4m->dst_c_dec_v=1; + _y4m->dst_buf_read_sz=_y4m->pic_w*_y4m->pic_h; + /*Chroma filter required: read into the aux buf first.*/ + _y4m->aux_buf_sz=_y4m->aux_buf_read_sz=2*((_y4m->pic_w+1)/2)*_y4m->pic_h; + _y4m->convert=y4m_convert_42xmpeg2_42xjpeg; + } + else if(strcmp(_y4m->chroma_type,"411")==0){ + _y4m->src_c_dec_h=4; + /*We don't want to introduce any additional sub-sampling, so we + promote 4:1:1 material to 4:2:2, as the closest format Theora can + handle.*/ + _y4m->dst_c_dec_h=2; + _y4m->src_c_dec_v=_y4m->dst_c_dec_v=1; + _y4m->dst_buf_read_sz=_y4m->pic_w*_y4m->pic_h; + /*Chroma filter required: read into the aux buf first.*/ + _y4m->aux_buf_sz=_y4m->aux_buf_read_sz=2*((_y4m->pic_w+3)/4)*_y4m->pic_h; + _y4m->convert=y4m_convert_411_422jpeg; + } + else if(strcmp(_y4m->chroma_type,"444")==0){ + _y4m->src_c_dec_h=_y4m->dst_c_dec_h=_y4m->src_c_dec_v=_y4m->dst_c_dec_v=1; + _y4m->dst_buf_read_sz=_y4m->pic_w*_y4m->pic_h*3; + /*Natively supported: no conversion required.*/ + _y4m->aux_buf_sz=_y4m->aux_buf_read_sz=0; + _y4m->convert=y4m_convert_null; + } + else if(strcmp(_y4m->chroma_type,"444alpha")==0){ + _y4m->src_c_dec_h=_y4m->dst_c_dec_h=_y4m->src_c_dec_v=_y4m->dst_c_dec_v=1; + _y4m->dst_buf_read_sz=_y4m->pic_w*_y4m->pic_h*3; + /*Read the extra alpha plane into the aux buf. + It will be discarded.*/ + _y4m->aux_buf_sz=_y4m->aux_buf_read_sz=_y4m->pic_w*_y4m->pic_h; + _y4m->convert=y4m_convert_null; + } + else if(strcmp(_y4m->chroma_type,"mono")==0){ + _y4m->src_c_dec_h=_y4m->src_c_dec_v=0; + _y4m->dst_c_dec_h=_y4m->dst_c_dec_v=2; + _y4m->dst_buf_read_sz=_y4m->pic_w*_y4m->pic_h; + /*No extra space required, but we need to clear the chroma planes.*/ + _y4m->aux_buf_sz=_y4m->aux_buf_read_sz=0; + _y4m->convert=y4m_convert_mono_420jpeg; + } + else{ + fprintf(stderr,"Unknown chroma sampling type: %s\n",_y4m->chroma_type); + return -1; + } + /*The size of the final frame buffers is always computed from the + destination chroma decimation type.*/ + _y4m->dst_buf_sz=_y4m->pic_w*_y4m->pic_h + +2*((_y4m->pic_w+_y4m->dst_c_dec_h-1)/_y4m->dst_c_dec_h)* + ((_y4m->pic_h+_y4m->dst_c_dec_v-1)/_y4m->dst_c_dec_v); + /*Scale the picture size up to a multiple of 16.*/ + _y4m->frame_w=_y4m->pic_w+15&~0xF; + _y4m->frame_h=_y4m->pic_h+15&~0xF; + /*Force the offsets to be even so that chroma samples line up like we + expect.*/ + _y4m->pic_x=_y4m->frame_w-_y4m->pic_w>>1&~1; + _y4m->pic_y=_y4m->frame_h-_y4m->pic_h>>1&~1; + _y4m->dst_buf=(unsigned char *)malloc(_y4m->dst_buf_sz); + _y4m->aux_buf=(unsigned char *)malloc(_y4m->aux_buf_sz); + return 0; +} + +static void y4m_input_get_info(y4m_input *_y4m,th_info *_ti){ + _ti->frame_width=_y4m->frame_w; + _ti->frame_height=_y4m->frame_h; + _ti->pic_width=_y4m->pic_w; + _ti->pic_height=_y4m->pic_h; + _ti->pic_x=_y4m->pic_x; + _ti->pic_y=_y4m->pic_y; + _ti->fps_numerator=_y4m->fps_n; + _ti->fps_denominator=_y4m->fps_d; + _ti->aspect_numerator=_y4m->par_n; + _ti->aspect_denominator=_y4m->par_d; + _ti->pixel_fmt=_y4m->dst_c_dec_h==2? + (_y4m->dst_c_dec_v==2?TH_PF_420:TH_PF_422):TH_PF_444; +} + +static int y4m_input_fetch_frame(y4m_input *_y4m,FILE *_fin, + th_ycbcr_buffer _ycbcr){ + char frame[6]; + int pic_sz; + int frame_c_w; + int frame_c_h; + int c_w; + int c_h; + int c_sz; + int ret; + pic_sz=_y4m->pic_w*_y4m->pic_h; + frame_c_w=_y4m->frame_w/_y4m->dst_c_dec_h; + frame_c_h=_y4m->frame_h/_y4m->dst_c_dec_v; + c_w=(_y4m->pic_w+_y4m->dst_c_dec_h-1)/_y4m->dst_c_dec_h; + c_h=(_y4m->pic_h+_y4m->dst_c_dec_v-1)/_y4m->dst_c_dec_v; + c_sz=c_w*c_h; + /*Read and skip the frame header.*/ + ret=fread(frame,1,6,_fin); + if(ret<6)return 0; + if(memcmp(frame,"FRAME",5)){ + fprintf(stderr,"Loss of framing in YUV input data\n"); + exit(1); + } + if(frame[5]!='\n'){ + char c; + int j; + for(j=0;j<79&&fread(&c,1,1,_fin)&&c!='\n';j++); + if(j==79){ + fprintf(stderr,"Error parsing YUV frame header\n"); + return -1; + } + } + /*Read the frame data that needs no conversion.*/ + if(fread(_y4m->dst_buf,1,_y4m->dst_buf_read_sz,_fin)!=_y4m->dst_buf_read_sz){ + fprintf(stderr,"Error reading YUV frame data.\n"); + return -1; + } + /*Read the frame data that does need conversion.*/ + if(fread(_y4m->aux_buf,1,_y4m->aux_buf_read_sz,_fin)!=_y4m->aux_buf_read_sz){ + fprintf(stderr,"Error reading YUV frame data.\n"); + return -1; + } + /*Now convert the just read frame.*/ + (*_y4m->convert)(_y4m,_y4m->dst_buf,_y4m->aux_buf); + /*Fill in the frame buffer pointers.*/ + _ycbcr[0].width=_y4m->frame_w; + _ycbcr[0].height=_y4m->frame_h; + _ycbcr[0].stride=_y4m->pic_w; + _ycbcr[0].data=_y4m->dst_buf-_y4m->pic_x-_y4m->pic_y*_y4m->pic_w; + _ycbcr[1].width=frame_c_w; + _ycbcr[1].height=frame_c_h; + _ycbcr[1].stride=c_w; + _ycbcr[1].data=_y4m->dst_buf+pic_sz-(_y4m->pic_x/_y4m->dst_c_dec_h)- + (_y4m->pic_y/_y4m->dst_c_dec_v)*c_w; + _ycbcr[2].width=frame_c_w; + _ycbcr[2].height=frame_c_h; + _ycbcr[2].stride=c_w; + _ycbcr[2].data=_ycbcr[1].data+c_sz; + return 1; +} + +static void y4m_input_close(y4m_input *_y4m){ + free(_y4m->dst_buf); + free(_y4m->aux_buf); +} + + + +typedef struct th_input th_input; + +struct th_input{ + ogg_sync_state oy; + int theora_p; + ogg_stream_state to; + th_info ti; + th_comment tc; + th_dec_ctx *td; +}; + + + +/*Grab some more compressed bitstream and sync it for page extraction.*/ +static int th_input_buffer_data(th_input *_th,FILE *_fin){ + char *buffer; + int bytes; + buffer=ogg_sync_buffer(&_th->oy,4096); + bytes=fread(buffer,1,4096,_fin); + ogg_sync_wrote(&_th->oy,bytes); + return bytes; +} + +/*Push a page into the appropriate steam. + This can be done blindly; a stream won't accept a page that doesn't belong to + it.*/ +static void th_input_queue_page(th_input *_th,ogg_page *_og){ + if(_th->theora_p)ogg_stream_pagein(&_th->to,_og); +} + +static int th_input_open_impl(th_input *_th,th_setup_info **_ts,FILE *_fin, + char *_sig,int _nsig){ + ogg_packet op; + ogg_page og; + int nheaders_left; + int done_headers; + ogg_sync_init(&_th->oy); + th_info_init(&_th->ti); + th_comment_init(&_th->tc); + *_ts=NULL; + /*Buffer any initial data read for file ID.*/ + if(_nsig>0){ + char *buffer; + buffer=ogg_sync_buffer(&_th->oy,_nsig); + memcpy(buffer,_sig,_nsig); + ogg_sync_wrote(&_th->oy,_nsig); + } + _th->theora_p=0; + nheaders_left=0; + for(done_headers=0;!done_headers;){ + if(th_input_buffer_data(_th,_fin)==0)break; + while(ogg_sync_pageout(&_th->oy,&og)>0){ + ogg_stream_state test; + /*Is this a mandated initial header? + If not, stop parsing.*/ + if(!ogg_page_bos(&og)){ + /*Don't leak the page; get it into the appropriate stream.*/ + th_input_queue_page(_th,&og); + done_headers=1; + break; + } + ogg_stream_init(&test,ogg_page_serialno(&og)); + ogg_stream_pagein(&test,&og); + ogg_stream_packetpeek(&test,&op); + /*Identify the codec: try Theora.*/ + if(!_th->theora_p){ + nheaders_left=th_decode_headerin(&_th->ti,&_th->tc,_ts,&op); + if(nheaders_left>=0){ + /*It is Theora.*/ + memcpy(&_th->to,&test,sizeof(test)); + _th->theora_p=1; + /*Advance past the successfully processed header.*/ + if(nheaders_left>0)ogg_stream_packetout(&_th->to,NULL); + continue; + } + } + /*Whatever it is, we don't care about it.*/ + ogg_stream_clear(&test); + } + } + /*We're expecting more header packets.*/ + while(_th->theora_p&&nheaders_left>0){ + int ret; + while(nheaders_left>0){ + ret=ogg_stream_packetpeek(&_th->to,&op); + if(ret==0)break; + if(ret<0)continue; + nheaders_left=th_decode_headerin(&_th->ti,&_th->tc,_ts,&op); + if(nheaders_left<0){ + fprintf(stderr,"Error parsing Theora stream headers; " + "corrupt stream?\n"); + return -1; + } + /*Advance past the successfully processed header.*/ + else if(nheaders_left>0)ogg_stream_packetout(&_th->to,NULL); + _th->theora_p++; + } + /*Stop now so we don't fail if there aren't enough pages in a short + stream.*/ + if(!(_th->theora_p&&nheaders_left>0))break; + /*The header pages/packets will arrive before anything else we care + about, or the stream is not obeying spec.*/ + if(ogg_sync_pageout(&_th->oy,&og)>0)th_input_queue_page(_th,&og); + /*We need more data.*/ + else if(th_input_buffer_data(_th,_fin)==0){ + fprintf(stderr,"End of file while searching for codec headers.\n"); + return -1; + } + } + /*And now we have it all. + Initialize the decoder.*/ + if(_th->theora_p){ + _th->td=th_decode_alloc(&_th->ti,*_ts); + if(_th->td!=NULL){ + fprintf(stderr,"Ogg logical stream %lx is Theora %ix%i %.02f fps video.\n" + "Encoded frame content is %ix%i with %ix%i offset.\n", + _th->to.serialno,_th->ti.frame_width,_th->ti.frame_height, + (double)_th->ti.fps_numerator/_th->ti.fps_denominator, + _th->ti.pic_width,_th->ti.pic_height,_th->ti.pic_x,_th->ti.pic_y); + return 1; + } + } + return -1; +} + +static void th_input_close(th_input *_th){ + if(_th->theora_p){ + ogg_stream_clear(&_th->to); + th_decode_free(_th->td); + } + th_comment_clear(&_th->tc); + th_info_clear(&_th->ti); + ogg_sync_clear(&_th->oy); +} + +static int th_input_open(th_input *_th,FILE *_fin,char *_sig,int _nsig){ + th_input th; + th_setup_info *ts; + int ret; + ret=th_input_open_impl(&th,&ts,_fin,_sig,_nsig); + th_setup_free(ts); + /*Clean up on failure.*/ + if(ret<0)th_input_close(&th); + else memcpy(_th,&th,sizeof(th)); + return ret; +} + +static void th_input_get_info(th_input *_th,th_info *_ti){ + memcpy(_ti,&_th->ti,sizeof(*_ti)); +} + +static int th_input_fetch_frame(th_input *_th,FILE *_fin, + th_ycbcr_buffer _ycbcr){ + for(;;){ + ogg_page og; + ogg_packet op; + if(ogg_stream_packetout(&_th->to,&op)>0){ + if(th_decode_packetin(_th->td,&op,NULL)>=0){ + th_decode_ycbcr_out(_th->td,_ycbcr); + if(!summary_only&&show_frame_type){ + printf("%c",th_packet_iskeyframe(&op)?'K':'D'); + if(op.bytes>0)printf("%02i ",op.packet[0]&0x3F); + else printf("-- "); + } + return 1; + } + else return -1; + } + while(ogg_sync_pageout(&_th->oy,&og)<=0){ + if(th_input_buffer_data(_th,_fin)==0)return feof(_fin)?0:-1; + } + th_input_queue_page(_th,&og); + } +} + + + +typedef struct video_input video_input; +typedef void (*video_input_get_info_func)(void *_ctx,th_info *_ti); +typedef int (*video_input_fetch_frame_func)(void *_ctx,FILE *_fin, + th_ycbcr_buffer _ycbcr); +typedef void (*video_input_close_func)(void *_ctx); + +struct video_input{ + FILE *fin; + video_input_get_info_func get_info; + video_input_fetch_frame_func fetch_frame; + video_input_close_func close; + union{ + y4m_input y4m; + th_input th; + }ctx; +}; + +static int video_input_open(video_input *_vid,FILE *_fin){ + char buffer[4]; + int ret; + /* look for magic */ + ret=fread(buffer,1,4,_fin); + if(ret<4)fprintf(stderr,"EOF determining file type of file.\n"); + else{ + if(!memcmp(buffer,"YUV4",4)){ + if(y4m_input_open(&_vid->ctx.y4m,_fin,buffer,4)>=0){ + /*fprintf(stderr,"Original %s is %dx%d %.02f fps %s video.\n", + f,_y4m->pic_w,_y4m->pic_h,(double)_y4m->fps_n/_y4m->fps_d,_y4m->chroma_type);*/ + _vid->fin=_fin; + _vid->get_info=(video_input_get_info_func)y4m_input_get_info; + _vid->fetch_frame=(video_input_fetch_frame_func)y4m_input_fetch_frame; + _vid->close=(video_input_close_func)y4m_input_close; + return 0; + } + } + else if(!memcmp(buffer,"OggS",4)){ + if(th_input_open(&_vid->ctx.th,_fin,buffer,4)>=0){ + _vid->fin=_fin; + _vid->get_info=(video_input_get_info_func)th_input_get_info; + _vid->fetch_frame=(video_input_fetch_frame_func)th_input_fetch_frame; + _vid->close=(video_input_close_func)th_input_close; + return 0; + } + } + else fprintf(stderr,"Unknown file type.\n"); + } + return -1; +} + +static void video_input_get_info(video_input *_vid,th_info *_ti){ + (*_vid->get_info)(&_vid->ctx,_ti); +} + +static int video_input_fetch_frame(video_input *_vid,th_ycbcr_buffer _ycbcr){ + return (*_vid->fetch_frame)(&_vid->ctx,_vid->fin,_ycbcr); +} + +static void video_input_close(video_input *_vid){ + (*_vid->close)(&_vid->ctx); + fclose(_vid->fin); +} + + + +static void usage(char *_argv[]){ + fprintf(stderr,"Usage: %s [options] <video1> <video2>\n" + " <video1> and <video1> may be either YUV4MPEG or Ogg Theora files.\n\n" + " Options:\n\n" + " -f --frame-type Show frame type and QI value for each Theora frame.\n" + " -s --summary Only output the summary line.\n" + " -y --luma-only Only output values for the luma channel.\n",_argv[0]); +} + +int main(int _argc,char *_argv[]){ + video_input vid1; + th_info ti1; + video_input vid2; + th_info ti2; + ogg_int64_t gsqerr; + ogg_int64_t gnpixels; + ogg_int64_t gplsqerr[3]; + ogg_int64_t gplnpixels[3]; + int frameno; + FILE *fin; + int long_option_index; + int c; +#ifdef _WIN32 + /*We need to set stdin/stdout to binary mode on windows. + Beware the evil ifdef. + We avoid these where we can, but this one we cannot. + Don't add any more, you'll probably go to hell if you do.*/ + _setmode(_fileno(stdin),_O_BINARY); +#endif + /*Process option arguments.*/ + while((c=getopt_long(_argc,_argv,optstring,options,&long_option_index))!=EOF){ + switch(c){ + case 'f':show_frame_type=1;break; + case 's':summary_only=1;break; + case 'y':luma_only=1;break; + default:usage(_argv);break; + } + } + if(optind+2!=_argc){ + usage(_argv); + exit(1); + } + fin=strcmp(_argv[optind],"-")==0?stdin:fopen(_argv[optind],"rb"); + if(fin==NULL){ + fprintf(stderr,"Unable to open '%s' for extraction.\n",_argv[optind]); + exit(1); + } + fprintf(stderr,"Opening %s...\n",_argv[optind]); + if(video_input_open(&vid1,fin)<0)exit(1); + video_input_get_info(&vid1,&ti1); + fin=strcmp(_argv[optind+1],"-")==0?stdin:fopen(_argv[optind+1],"rb"); + if(fin==NULL){ + fprintf(stderr,"Unable to open '%s' for extraction.\n",_argv[optind+1]); + exit(1); + } + fprintf(stderr,"Opening %s...\n",_argv[optind+1]); + if(video_input_open(&vid2,fin)<0)exit(1); + video_input_get_info(&vid2,&ti2); + /*Check to make sure these videos are compatible.*/ + if(ti1.pic_width!=ti2.pic_width||ti1.pic_height!=ti2.pic_height){ + fprintf(stderr,"Video resolution does not match.\n"); + exit(1); + } + if(ti1.pixel_fmt!=ti2.pixel_fmt){ + fprintf(stderr,"Pixel formats do not match.\n"); + exit(1); + } + if((ti1.pic_x&!(ti1.pixel_fmt&1))!=(ti2.pic_x&!(ti2.pixel_fmt&1))|| + (ti1.pic_y&!(ti1.pixel_fmt&2))!=(ti2.pic_y&!(ti2.pixel_fmt&2))){ + fprintf(stderr,"Chroma subsampling offsets do not match.\n"); + exit(1); + } + if(ti1.fps_numerator*(ogg_int64_t)ti2.fps_denominator!= + ti2.fps_numerator*(ogg_int64_t)ti1.fps_denominator){ + fprintf(stderr,"Warning: framerates do not match.\n"); + } + if(ti1.aspect_numerator*(ogg_int64_t)ti2.aspect_denominator!= + ti2.aspect_numerator*(ogg_int64_t)ti1.aspect_denominator){ + fprintf(stderr,"Warning: aspect ratios do not match.\n"); + } + gsqerr=gplsqerr[0]=gplsqerr[1]=gplsqerr[2]=0; + gnpixels=gplnpixels[0]=gplnpixels[1]=gplnpixels[2]=0; + for(frameno=0;;frameno++){ + th_ycbcr_buffer f1; + th_ycbcr_buffer f2; + ogg_int64_t plsqerr[3]; + long plnpixels[3]; + ogg_int64_t sqerr; + long npixels; + int ret1; + int ret2; + int pli; + ret1=video_input_fetch_frame(&vid1,f1); + ret2=video_input_fetch_frame(&vid2,f2); + if(ret1==0&&ret2==0)break; + else if(ret1<0||ret2<0)break; + else if(ret1==0){ + fprintf(stderr,"%s ended before %s.\n", + _argv[optind],_argv[optind+1]); + break; + } + else if(ret2==0){ + fprintf(stderr,"%s ended before %s.\n", + _argv[optind+1],_argv[optind]); + break; + } + /*Okay, we got one frame from each.*/ + sqerr=0; + npixels=0; + for(pli=0;pli<3;pli++){ + int xdec; + int ydec; + int y1; + int y2; + xdec=pli&&!(ti1.pixel_fmt&1); + ydec=pli&&!(ti1.pixel_fmt&2); + plsqerr[pli]=0; + plnpixels[pli]=0; + for(y1=ti1.pic_y>>ydec,y2=ti2.pic_y>>ydec; + y1<ti1.pic_y+ti1.pic_height+ydec>>ydec;y1++,y2++){ + int x1; + int x2; + for(x1=ti1.pic_x>>xdec,x2=ti2.pic_x>>xdec; + x1<ti1.pic_x+ti1.pic_width+xdec>>xdec;x1++,x2++){ + int d; + d=*(f1[pli].data+y1*f1[pli].stride+x1)- + *(f2[pli].data+y2*f2[pli].stride+x2); + plsqerr[pli]+=d*d; + plnpixels[pli]++; + } + } + sqerr+=plsqerr[pli]; + gplsqerr[pli]+=plsqerr[pli]; + npixels+=plnpixels[pli]; + gplnpixels[pli]+=plnpixels[pli]; + } + if(!summary_only){ + if(!luma_only){ + printf("%08i: %-7lG (Y': %-7lG Cb: %-7lG Cr: %-7lG)\n",frameno, + 10*(log10(255*255)+log10(npixels)-log10(sqerr)), + 10*(log10(255*255)+log10(plnpixels[0])-log10(plsqerr[0])), + 10*(log10(255*255)+log10(plnpixels[1])-log10(plsqerr[1])), + 10*(log10(255*255)+log10(plnpixels[2])-log10(plsqerr[2]))); + } + else{ + printf("%08i: %-7lG\n",frameno, + 10*(log10(255*255)+log10(plnpixels[0])-log10(plsqerr[0]))); + } + } + gsqerr+=sqerr; + gnpixels+=npixels; + } + if(!luma_only){ + printf("Total: %-7lG (Y': %-7lG Cb: %-7lG Cr: %-7lG)\n", + 10*(log10(255*255)+log10(gnpixels)-log10(gsqerr)), + 10*(log10(255*255)+log10(gplnpixels[0])-log10(gplsqerr[0])), + 10*(log10(255*255)+log10(gplnpixels[1])-log10(gplsqerr[1])), + 10*(log10(255*255)+log10(gplnpixels[2])-log10(gplsqerr[2]))); + } + else{ + printf("Total: %-7lG\n", + 10*(log10(255*255)+log10(gplnpixels[0])-log10(gplsqerr[0]))); + } + video_input_close(&vid1); + video_input_close(&vid2); + return 0; +} diff --git a/examples/dump_video.c b/examples/dump_video.c new file mode 100644 index 0000000..c4ad468 --- /dev/null +++ b/examples/dump_video.c @@ -0,0 +1,496 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: example dumpvid application; dumps Theora streams + last mod: $Id: dump_video.c,v 1.2 2004/03/24 19:12:42 derf Exp $ + + ********************************************************************/ + +/* By Mauricio Piacentini (mauricio at xiph.org) */ +/* simply dump decoded YUV data, for verification of theora bitstream */ + +#if !defined(_REENTRANT) +#define _REENTRANT +#endif +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif +#if !defined(_LARGEFILE_SOURCE) +#define _LARGEFILE_SOURCE +#endif +#if !defined(_LARGEFILE64_SOURCE) +#define _LARGEFILE64_SOURCE +#endif +#if !defined(_FILE_OFFSET_BITS) +#define _FILE_OFFSET_BITS 64 +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/timeb.h> +#include <sys/types.h> +#include <sys/stat.h> +/*Yes, yes, we're going to hell.*/ +#if defined(_WIN32) +#include <io.h> +#endif +#include <fcntl.h> +#include <limits.h> +#include <math.h> +#include <signal.h> +#include "getopt.h" +#include "theora/theoradec.h" + +const char *optstring = "o:rf"; +struct option options [] = { + {"output",required_argument,NULL,'o'}, + {"raw",no_argument, NULL,'r'}, /*Disable YUV4MPEG2 headers:*/ + {"fps-only",no_argument, NULL, 'f'}, /* Only interested in fps of decode loop */ + {NULL,0,NULL,0} +}; + +/* Helper; just grab some more compressed bitstream and sync it for + page extraction */ +int buffer_data(FILE *in,ogg_sync_state *oy){ + char *buffer=ogg_sync_buffer(oy,4096); + int bytes=fread(buffer,1,4096,in); + ogg_sync_wrote(oy,bytes); + return(bytes); +} + +/* never forget that globals are a one-way ticket to Hell */ +/* Ogg and codec state for demux/decode */ +ogg_sync_state oy; +ogg_page og; +ogg_stream_state vo; +ogg_stream_state to; +th_info ti; +th_comment tc; +th_setup_info *ts; +th_dec_ctx *td; + +int theora_p=0; +int theora_processing_headers; +int stateflag=0; + +/* single frame video buffering */ +int videobuf_ready=0; +ogg_int64_t videobuf_granulepos=-1; +double videobuf_time=0; +int raw=0; + +FILE* outfile = NULL; + +int got_sigint=0; +static void sigint_handler (int signal) { + got_sigint = 1; +} + +static th_ycbcr_buffer ycbcr; + +static void stripe_decoded(th_ycbcr_buffer _dst,th_ycbcr_buffer _src, + int _fragy0,int _fragy_end){ + int pli; + for(pli=0;pli<3;pli++){ + int yshift; + int y_end; + int y; + yshift=pli!=0&&!(ti.pixel_fmt&2); + y_end=_fragy_end<<3-yshift; + /*An implemention intending to display this data would need to check the + crop rectangle before proceeding.*/ + for(y=_fragy0<<3-yshift;y<y_end;y++){ + memcpy(_dst[pli].data+y*_dst[pli].stride, + _src[pli].data+y*_src[pli].stride,_src[pli].width); + } + } +} + +static void open_video(void){ + th_stripe_callback cb; + int pli; + /*Here we allocate a buffer so we can use the striped decode feature. + There's no real reason to do this in this application, because we want to + write to the file top-down, but the frame gets decoded bottom up, so we + have to buffer it all anyway. + But this illustrates how the API works.*/ + for(pli=0;pli<3;pli++){ + int xshift; + int yshift; + xshift=pli!=0&&!(ti.pixel_fmt&1); + yshift=pli!=0&&!(ti.pixel_fmt&2); + ycbcr[pli].data=(unsigned char *)malloc( + (ti.frame_width>>xshift)*(ti.frame_height>>yshift)*sizeof(char)); + ycbcr[pli].stride=ti.frame_width>>xshift; + ycbcr[pli].width=ti.frame_width>>xshift; + ycbcr[pli].height=ti.frame_height>>yshift; + } + /*Similarly, since ycbcr is a global, there's no real reason to pass it as + the context. + In a more object-oriented decoder, we could pass the "this" pointer + instead (though in C++, platform-dependent calling convention differences + prevent us from using a real member function pointer).*/ + cb.ctx=ycbcr; + cb.stripe_decoded=(th_stripe_decoded_func)stripe_decoded; + th_decode_ctl(td,TH_DECCTL_SET_STRIPE_CB,&cb,sizeof(cb)); +} + +/*Write out the planar YUV frame, uncropped.*/ +static void video_write(void){ + int pli; + int i; + /*Uncomment the following to do normal, non-striped decoding. + th_ycbcr_buffer ycbcr; + th_decode_ycbcr_out(td,ycbcr);*/ + if(outfile){ + if(!raw)fprintf(outfile, "FRAME\n"); + for(pli=0;pli<3;pli++){ + for(i=0;i<ycbcr[pli].height;i++){ + fwrite(ycbcr[pli].data+ycbcr[pli].stride*i, 1, + ycbcr[pli].width, outfile); + } + } + } +} + +/* dump the theora comment header */ +static int dump_comments(th_comment *_tc){ + int i; + int len; + FILE *out; + out=stderr; + fprintf(out,"Encoded by %s\n",_tc->vendor); + if(_tc->comments){ + fprintf(out,"theora comment header:\n"); + for(i=0;i<_tc->comments;i++){ + if(_tc->user_comments[i]){ + len=_tc->comment_lengths[i]<INT_MAX?_tc->comment_lengths[i]:INT_MAX; + fprintf(out,"\t%.*s\n",len,_tc->user_comments[i]); + } + } + } + return 0; +} + +/* helper: push a page into the appropriate steam */ +/* this can be done blindly; a stream won't accept a page + that doesn't belong to it */ +static int queue_page(ogg_page *page){ + if(theora_p)ogg_stream_pagein(&to,page); + return 0; +} + +static void usage(void){ + fprintf(stderr, + "Usage: dumpvid <file.ogv> > outfile\n" + "input is read from stdin if no file is passed on the command line\n" + "\n" + ); +} + +int main(int argc,char *argv[]){ + + ogg_packet op; + + int long_option_index; + int c; + + struct timeb start; + struct timeb after; + struct timeb last; + int fps_only=0; + int frames = 0; + + FILE *infile = stdin; + outfile = stdout; + +#ifdef _WIN32 /* We need to set stdin/stdout to binary mode on windows. */ + /* Beware the evil ifdef. We avoid these where we can, but this one we + cannot. Don't add any more, you'll probably go to hell if you do. */ + _setmode( _fileno( stdin ), _O_BINARY ); + _setmode( _fileno( stdout ), _O_BINARY ); +#endif + + /* Process option arguments. */ + while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){ + switch(c){ + case 'o': + if(strcmp(optarg,"-")!=0){ + outfile=fopen(optarg,"wb"); + if(outfile==NULL){ + fprintf(stderr,"Unable to open output file '%s'\n", optarg); + exit(1); + } + }else{ + outfile=stdout; + } + break; + + case 'r': + raw=1; + break; + + case 'f': + fps_only = 1; + outfile = NULL; + break; + + default: + usage(); + } + } + if(optind<argc){ + infile=fopen(argv[optind],"rb"); + if(infile==NULL){ + fprintf(stderr,"Unable to open '%s' for extraction.\n", argv[optind]); + exit(1); + } + if(++optind<argc){ + usage(); + exit(1); + } + } + /*Ok, Ogg parsing. + The idea here is we have a bitstream that is made up of Ogg pages. + The libogg sync layer will find them for us. + There may be pages from several logical streams interleaved; we find the + first theora stream and ignore any others. + Then we pass the pages for our stream to the libogg stream layer which + assembles our original set of packets out of them. + It's the packets that libtheora actually knows how to handle.*/ + + /* start up Ogg stream synchronization layer */ + ogg_sync_init(&oy); + + /* init supporting Theora structures needed in header parsing */ + th_comment_init(&tc); + th_info_init(&ti); + + /*Ogg file open; parse the headers. + Theora (like Vorbis) depends on some initial header packets for decoder + setup and initialization. + We retrieve these first before entering the main decode loop.*/ + + /* Only interested in Theora streams */ + while(!stateflag){ + int ret=buffer_data(infile,&oy); + if(ret==0)break; + while(ogg_sync_pageout(&oy,&og)>0){ + int got_packet; + ogg_stream_state test; + + /* is this a mandated initial header? If not, stop parsing */ + if(!ogg_page_bos(&og)){ + /* don't leak the page; get it into the appropriate stream */ + queue_page(&og); + stateflag=1; + break; + } + + ogg_stream_init(&test,ogg_page_serialno(&og)); + ogg_stream_pagein(&test,&og); + got_packet = ogg_stream_packetpeek(&test,&op); + + /* identify the codec: try theora */ + if((got_packet==1) && !theora_p && (theora_processing_headers= + th_decode_headerin(&ti,&tc,&ts,&op))>=0){ + /* it is theora -- save this stream state */ + memcpy(&to,&test,sizeof(test)); + theora_p=1; + /*Advance past the successfully processed header.*/ + if(theora_processing_headers)ogg_stream_packetout(&to,NULL); + }else{ + /* whatever it is, we don't care about it */ + ogg_stream_clear(&test); + } + } + /* fall through to non-bos page parsing */ + } + + /* we're expecting more header packets. */ + while(theora_p && theora_processing_headers){ + int ret; + + /* look for further theora headers */ + while(theora_processing_headers&&(ret=ogg_stream_packetpeek(&to,&op))){ + if(ret<0)continue; + theora_processing_headers=th_decode_headerin(&ti,&tc,&ts,&op); + if(theora_processing_headers<0){ + fprintf(stderr,"Error parsing Theora stream headers; " + "corrupt stream?\n"); + exit(1); + } + else if(theora_processing_headers>0){ + /*Advance past the successfully processed header.*/ + ogg_stream_packetout(&to,NULL); + } + theora_p++; + } + + /*Stop now so we don't fail if there aren't enough pages in a short + stream.*/ + if(!(theora_p && theora_processing_headers))break; + + /* The header pages/packets will arrive before anything else we + care about, or the stream is not obeying spec */ + + if(ogg_sync_pageout(&oy,&og)>0){ + queue_page(&og); /* demux into the appropriate stream */ + }else{ + int ret=buffer_data(infile,&oy); /* someone needs more data */ + if(ret==0){ + fprintf(stderr,"End of file while searching for codec headers.\n"); + exit(1); + } + } + } + + /* and now we have it all. initialize decoders */ + if(theora_p){ + dump_comments(&tc); + td=th_decode_alloc(&ti,ts); + fprintf(stderr,"Ogg logical stream %lx is Theora %dx%d %.02f fps video\n" + "Encoded frame content is %dx%d with %dx%d offset\n", + to.serialno,ti.frame_width,ti.frame_height, + (double)ti.fps_numerator/ti.fps_denominator, + ti.pic_width,ti.pic_height,ti.pic_x,ti.pic_y); + }else{ + /* tear down the partial theora setup */ + th_info_clear(&ti); + th_comment_clear(&tc); + } + /*Either way, we're done with the codec setup data.*/ + th_setup_free(ts); + + /* open video */ + if(theora_p)open_video(); + + if(!raw && outfile){ + static const char *CHROMA_TYPES[4]={"420jpeg",NULL,"422","444"}; + if(ti.pixel_fmt>=4||ti.pixel_fmt==TH_PF_RSVD){ + fprintf(stderr,"Unknown pixel format: %i\n",ti.pixel_fmt); + exit(1); + } + fprintf(outfile,"YUV4MPEG2 C%s W%d H%d F%d:%d I%c A%d:%d\n", + CHROMA_TYPES[ti.pixel_fmt],ti.frame_width,ti.frame_height, + ti.fps_numerator,ti.fps_denominator,'p', + ti.aspect_numerator,ti.aspect_denominator); + } + + /* install signal handler */ + signal (SIGINT, sigint_handler); + + /*Finally the main decode loop. + + It's one Theora packet per frame, so this is pretty straightforward if + we're not trying to maintain sync with other multiplexed streams. + + The videobuf_ready flag is used to maintain the input buffer in the libogg + stream state. + If there's no output frame available at the end of the decode step, we must + need more input data. + We could simplify this by just using the return code on + ogg_page_packetout(), but the flag system extends easily to the case where + you care about more than one multiplexed stream (like with audio + playback). + In that case, just maintain a flag for each decoder you care about, and + pull data when any one of them stalls. + + videobuf_time holds the presentation time of the currently buffered video + frame. + We ignore this value.*/ + + stateflag=0; /* playback has not begun */ + /* queue any remaining pages from data we buffered but that did not + contain headers */ + while(ogg_sync_pageout(&oy,&og)>0){ + queue_page(&og); + } + + if(fps_only){ + ftime(&start); + ftime(&last); + } + + while(!got_sigint){ + + while(theora_p && !videobuf_ready){ + /* theora is one in, one out... */ + if(ogg_stream_packetout(&to,&op)>0){ + + if(th_decode_packetin(td,&op,&videobuf_granulepos)>=0){ + videobuf_time=th_granule_time(td,videobuf_granulepos); + videobuf_ready=1; + frames++; + if(fps_only) + ftime(&after); + } + + }else + break; + } + + if(fps_only && (videobuf_ready || fps_only==2)){ + long ms = + after.time*1000.+after.millitm- + (last.time*1000.+last.millitm); + + if(ms>500 || fps_only==1 || + (feof(infile) && !videobuf_ready)){ + float file_fps = (float)ti.fps_numerator/ti.fps_denominator; + fps_only=2; + + ms = after.time*1000.+after.millitm- + (start.time*1000.+start.millitm); + + fprintf(stderr,"\rframe:%d rate:%.2fx ", + frames, + frames*1000./(ms*file_fps)); + memcpy(&last,&after,sizeof(last)); + } + } + + if(!videobuf_ready && feof(infile))break; + + if(!videobuf_ready){ + /* no data yet for somebody. Grab another page */ + buffer_data(infile,&oy); + while(ogg_sync_pageout(&oy,&og)>0){ + queue_page(&og); + } + } + /* dumpvideo frame, and get new one */ + else if(outfile)video_write(); + + videobuf_ready=0; + } + + /* end of decoder loop -- close everything */ + + if(theora_p){ + ogg_stream_clear(&to); + th_decode_free(td); + th_comment_clear(&tc); + th_info_clear(&ti); + } + ogg_sync_clear(&oy); + + if(infile && infile!=stdin)fclose(infile); + if(outfile && outfile!=stdout)fclose(outfile); + + fprintf(stderr, "\n\n%d frames\n", frames); + fprintf(stderr, "\nDone.\n"); + + return(0); + +} diff --git a/examples/encoder_example.c b/examples/encoder_example.c new file mode 100644 index 0000000..4b73b64 --- /dev/null +++ b/examples/encoder_example.c @@ -0,0 +1,1826 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009 * + * by the Xiph.Org Foundation and contributors http://www.xiph.org/ * + * * + ******************************************************************** + + function: example encoder application; makes an Ogg Theora/Vorbis + file from YUV4MPEG2 and WAV input + last mod: $Id: encoder_example.c 16517 2009-08-25 19:48:57Z giles $ + + ********************************************************************/ + +#if !defined(_REENTRANT) +#define _REENTRANT +#endif +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif +#if !defined(_LARGEFILE_SOURCE) +#define _LARGEFILE_SOURCE +#endif +#if !defined(_LARGEFILE64_SOURCE) +#define _LARGEFILE64_SOURCE +#endif +#if !defined(_FILE_OFFSET_BITS) +#define _FILE_OFFSET_BITS 64 +#endif + +#include <stdio.h> +#if !defined(_WIN32) +#include <getopt.h> +#include <unistd.h> +#else +#include "getopt.h" +#endif +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <math.h> +#include "theora/theoraenc.h" +#include "vorbis/codec.h" +#include "vorbis/vorbisenc.h" + +#ifdef _WIN32 +/*supply missing headers and functions to Win32. going to hell, I know*/ +#include <fcntl.h> +#include <io.h> + +static double rint(double x) +{ + if (x < 0.0) + return (double)(int)(x - 0.5); + else + return (double)(int)(x + 0.5); +} +#endif + +const char *optstring = "b:e:o:a:A:v:V:s:S:f:F:ck:d:z:\1\2\3\4"; +struct option options [] = { + {"begin-time",required_argument,NULL,'b'}, + {"end-time",required_argument,NULL,'e'}, + {"output",required_argument,NULL,'o'}, + {"audio-rate-target",required_argument,NULL,'A'}, + {"video-rate-target",required_argument,NULL,'V'}, + {"audio-quality",required_argument,NULL,'a'}, + {"video-quality",required_argument,NULL,'v'}, + {"aspect-numerator",required_argument,NULL,'s'}, + {"aspect-denominator",required_argument,NULL,'S'}, + {"framerate-numerator",required_argument,NULL,'f'}, + {"framerate-denominator",required_argument,NULL,'F'}, + {"vp3-compatible",no_argument,NULL,'c'}, + {"speed",required_argument,NULL,'z'}, + {"soft-target",no_argument,NULL,'\1'}, + {"keyframe-freq",required_argument,NULL,'k'}, + {"buf-delay",required_argument,NULL,'d'}, + {"two-pass",no_argument,NULL,'\2'}, + {"first-pass",required_argument,NULL,'\3'}, + {"second-pass",required_argument,NULL,'\4'}, + {NULL,0,NULL,0} +}; + +/* You'll go to Hell for using globals. */ + +FILE *audio=NULL; +FILE *video=NULL; + +int audio_ch=0; +int audio_hz=0; + +float audio_q=.1f; +int audio_r=-1; +int vp3_compatible=0; + +int frame_w=0; +int frame_h=0; +int pic_w=0; +int pic_h=0; +int pic_x=0; +int pic_y=0; +int video_fps_n=-1; +int video_fps_d=-1; +int video_par_n=-1; +int video_par_d=-1; +char interlace; +int src_c_dec_h=2; +int src_c_dec_v=2; +int dst_c_dec_h=2; +int dst_c_dec_v=2; +char chroma_type[16]; + +/*The size of each converted frame buffer.*/ +size_t y4m_dst_buf_sz; +/*The amount to read directly into the converted frame buffer.*/ +size_t y4m_dst_buf_read_sz; +/*The size of the auxilliary buffer.*/ +size_t y4m_aux_buf_sz; +/*The amount to read into the auxilliary buffer.*/ +size_t y4m_aux_buf_read_sz; + +/*The function used to perform chroma conversion.*/ +typedef void (*y4m_convert_func)(unsigned char *_dst,unsigned char *_aux); + +y4m_convert_func y4m_convert=NULL; + +int video_r=-1; +int video_q=-1; +ogg_uint32_t keyframe_frequency=0; +int buf_delay=-1; + +long begin_sec=-1; +long begin_usec=0; +long end_sec=-1; +long end_usec=0; + +static void usage(void){ + fprintf(stderr, + "Usage: encoder_example [options] [audio_file] video_file\n\n" + "Options: \n\n" + " -o --output <filename.ogv> file name for encoded output;\n" + " If this option is not given, the\n" + " compressed data is sent to stdout.\n\n" + " -A --audio-rate-target <n> bitrate target for Vorbis audio;\n" + " use -a and not -A if at all possible,\n" + " as -a gives higher quality for a given\n" + " bitrate.\n\n" + " -V --video-rate-target <n> bitrate target for Theora video\n\n" + " --soft-target Use a large reservoir and treat the rate\n" + " as a soft target; rate control is less\n" + " strict but resulting quality is usually\n" + " higher/smoother overall. Soft target also\n" + " allows an optional -v setting to specify\n" + " a minimum allowed quality.\n\n" + " --two-pass Compress input using two-pass rate control\n" + " This option requires that the input to the\n" + " to the encoder is seekable and performs\n" + " both passes automatically.\n\n" + " --first-pass <filename> Perform first-pass of a two-pass rate\n" + " controlled encoding, saving pass data to\n" + " <filename> for a later second pass\n\n" + " --second-pass <filename> Perform second-pass of a two-pass rate\n" + " controlled encoding, reading first-pass\n" + " data from <filename>. The first pass\n" + " data must come from a first encoding pass\n" + " using identical input video to work\n" + " properly.\n\n" + " -a --audio-quality <n> Vorbis quality selector from -1 to 10\n" + " (-1 yields smallest files but lowest\n" + " fidelity; 10 yields highest fidelity\n" + " but large files. '2' is a reasonable\n" + " default).\n\n" + " -v --video-quality <n> Theora quality selector from 0 to 10\n" + " (0 yields smallest files but lowest\n" + " video quality. 10 yields highest\n" + " fidelity but large files).\n\n" + " -s --aspect-numerator <n> Aspect ratio numerator, default is 0\n" + " or extracted from YUV input file\n" + " -S --aspect-denominator <n> Aspect ratio denominator, default is 0\n" + " or extracted from YUV input file\n" + " -f --framerate-numerator <n> Frame rate numerator, can be extracted\n" + " from YUV input file. ex: 30000000\n" + " -F --framerate-denominator <n> Frame rate denominator, can be extracted\n" + " from YUV input file. ex: 1000000\n" + " The frame rate nominator divided by this\n" + " determinates the frame rate in units per tick\n" + " -k --keyframe-freq <n> Keyframe frequency\n" + " -z --speed <n> Sets the encoder speed level. Higher speed\n" + " levels favor quicker encoding over better\n" + " quality per bit. Depending on the encoding\n" + " mode, and the internal algorithms used,\n" + " quality may actually improve with higher\n" + " speeds, but in this case bitrate will also\n" + " likely increase. The maximum value, and the\n" + " meaning of each value, are implementation-\n" + " specific and may change depending on the\n" + " current encoding mode (rate constrained,\n" + " two-pass, etc.).\n" + " -d --buf-delay <n> Buffer delay (in frames). Longer delays\n" + " allow smoother rate adaptation and provide\n" + " better overall quality, but require more\n" + " client side buffering and add latency. The\n" + " default value is the keyframe interval for\n" + " one-pass encoding (or somewhat larger if\n" + " --soft-target is used) and infinite for\n" + " two-pass encoding.\n" + " -b --begin-time <h:m:s.d> Begin encoding at offset into input\n" + " -e --end-time <h:m:s.d> End encoding at offset into input\n" + "encoder_example accepts only uncompressed RIFF WAV format audio and\n" + "YUV4MPEG2 uncompressed video.\n\n"); + exit(1); +} + +static int y4m_parse_tags(char *_tags){ + int got_w; + int got_h; + int got_fps; + int got_interlace; + int got_par; + int got_chroma; + int tmp_video_fps_n; + int tmp_video_fps_d; + int tmp_video_par_n; + int tmp_video_par_d; + char *p; + char *q; + got_w=got_h=got_fps=got_interlace=got_par=got_chroma=0; + for(p=_tags;;p=q){ + /*Skip any leading spaces.*/ + while(*p==' ')p++; + /*If that's all we have, stop.*/ + if(p[0]=='\0')break; + /*Find the end of this tag.*/ + for(q=p+1;*q!='\0'&&*q!=' ';q++); + /*Process the tag.*/ + switch(p[0]){ + case 'W':{ + if(sscanf(p+1,"%d",&pic_w)!=1)return -1; + got_w=1; + }break; + case 'H':{ + if(sscanf(p+1,"%d",&pic_h)!=1)return -1; + got_h=1; + }break; + case 'F':{ + if(sscanf(p+1,"%d:%d",&tmp_video_fps_n,&tmp_video_fps_d)!=2)return -1; + got_fps=1; + }break; + case 'I':{ + interlace=p[1]; + got_interlace=1; + }break; + case 'A':{ + if(sscanf(p+1,"%d:%d",&tmp_video_par_n,&tmp_video_par_d)!=2)return -1; + got_par=1; + }break; + case 'C':{ + if(q-p>16)return -1; + memcpy(chroma_type,p+1,q-p-1); + chroma_type[q-p-1]='\0'; + got_chroma=1; + }break; + /*Ignore unknown tags.*/ + } + } + if(!got_w||!got_h||!got_fps||!got_interlace||!got_par)return -1; + /*Chroma-type is not specified in older files, e.g., those generated by + mplayer.*/ + if(!got_chroma)strcpy(chroma_type,"420"); + /*Update fps and aspect ratio globals if not specified in the command line.*/ + if(video_fps_n==-1)video_fps_n=tmp_video_fps_n; + if(video_fps_d==-1)video_fps_d=tmp_video_fps_d; + if(video_par_n==-1)video_par_n=tmp_video_par_n; + if(video_par_d==-1)video_par_d=tmp_video_par_d; + return 0; +} + +/*All anti-aliasing filters in the following conversion functions are based on + one of two window functions: + The 6-tap Lanczos window (for down-sampling and shifts): + sinc(\pi*t)*sinc(\pi*t/3), |t|<3 (sinc(t)==sin(t)/t) + 0, |t|>=3 + The 4-tap Mitchell window (for up-sampling): + 7|t|^3-12|t|^2+16/3, |t|<1 + -(7/3)|x|^3+12|x|^2-20|x|+32/3, |t|<2 + 0, |t|>=2 + The number of taps is intentionally kept small to reduce computational + overhead and limit ringing. + + The taps from these filters are scaled so that their sum is 1, and the result + is scaled by 128 and rounded to integers to create a filter whose + intermediate values fit inside 16 bits. + Coefficients are rounded in such a way as to ensure their sum is still 128, + which is usually equivalent to normal rounding.*/ + +#define OC_MINI(_a,_b) ((_a)>(_b)?(_b):(_a)) +#define OC_MAXI(_a,_b) ((_a)<(_b)?(_b):(_a)) +#define OC_CLAMPI(_a,_b,_c) (OC_MAXI(_a,OC_MINI(_b,_c))) + +/*420jpeg chroma samples are sited like: + Y-------Y-------Y-------Y------- + | | | | + | BR | | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | BR | | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + + 420mpeg2 chroma samples are sited like: + Y-------Y-------Y-------Y------- + | | | | + BR | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + Y-------Y-------Y-------Y------- + | | | | + BR | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + + We use a resampling filter to shift the site locations one quarter pixel (at + the chroma plane's resolution) to the right. + The 4:2:2 modes look exactly the same, except there are twice as many chroma + lines, and they are vertically co-sited with the luma samples in both the + mpeg2 and jpeg cases (thus requiring no vertical resampling).*/ +static void y4m_convert_42xmpeg2_42xjpeg(unsigned char *_dst, + unsigned char *_aux){ + int c_w; + int c_h; + int pli; + int y; + int x; + /*Skip past the luma data.*/ + _dst+=pic_w*pic_h; + /*Compute the size of each chroma plane.*/ + c_w=(pic_w+dst_c_dec_h-1)/dst_c_dec_h; + c_h=(pic_h+dst_c_dec_v-1)/dst_c_dec_v; + for(pli=1;pli<3;pli++){ + for(y=0;y<c_h;y++){ + /*Filter: [4 -17 114 35 -9 1]/128, derived from a 6-tap Lanczos + window.*/ + for(x=0;x<OC_MINI(c_w,2);x++){ + _dst[x]=(unsigned char)OC_CLAMPI(0,4*_aux[0]-17*_aux[OC_MAXI(x-1,0)]+ + 114*_aux[x]+35*_aux[OC_MINI(x+1,c_w-1)]-9*_aux[OC_MINI(x+2,c_w-1)]+ + _aux[OC_MINI(x+3,c_w-1)]+64>>7,255); + } + for(;x<c_w-3;x++){ + _dst[x]=(unsigned char)OC_CLAMPI(0,4*_aux[x-2]-17*_aux[x-1]+ + 114*_aux[x]+35*_aux[x+1]-9*_aux[x+2]+_aux[x+3]+64>>7,255); + } + for(;x<c_w;x++){ + _dst[x]=(unsigned char)OC_CLAMPI(0,4*_aux[x-2]-17*_aux[x-1]+ + 114*_aux[x]+35*_aux[OC_MINI(x+1,c_w-1)]-9*_aux[OC_MINI(x+2,c_w-1)]+ + _aux[c_w-1]+64>>7,255); + } + _dst+=c_w; + _aux+=c_w; + } + } +} + +/*This format is only used for interlaced content, but is included for + completeness. + + 420jpeg chroma samples are sited like: + Y-------Y-------Y-------Y------- + | | | | + | BR | | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | BR | | BR | + | | | | + Y-------Y-------Y-------Y------- + | | | | + | | | | + | | | | + + 420paldv chroma samples are sited like: + YR------Y-------YR------Y------- + | | | | + | | | | + | | | | + YB------Y-------YB------Y------- + | | | | + | | | | + | | | | + YR------Y-------YR------Y------- + | | | | + | | | | + | | | | + YB------Y-------YB------Y------- + | | | | + | | | | + | | | | + + We use a resampling filter to shift the site locations one quarter pixel (at + the chroma plane's resolution) to the right. + Then we use another filter to move the C_r location down one quarter pixel, + and the C_b location up one quarter pixel.*/ +static void y4m_convert_42xpaldv_42xjpeg(unsigned char *_dst, + unsigned char *_aux){ + unsigned char *tmp; + int c_w; + int c_h; + int c_sz; + int pli; + int y; + int x; + /*Skip past the luma data.*/ + _dst+=pic_w*pic_h; + /*Compute the size of each chroma plane.*/ + c_w=(pic_w+1)/2; + c_h=(pic_h+dst_c_dec_h-1)/dst_c_dec_h; + c_sz=c_w*c_h; + /*First do the horizontal re-sampling. + This is the same as the mpeg2 case, except that after the horizontal case, + we need to apply a second vertical filter.*/ + tmp=_aux+2*c_sz; + for(pli=1;pli<3;pli++){ + for(y=0;y<c_h;y++){ + /*Filter: [4 -17 114 35 -9 1]/128, derived from a 6-tap Lanczos + window.*/ + for(x=0;x<OC_MINI(c_w,2);x++){ + tmp[x]=(unsigned char)OC_CLAMPI(0,4*_aux[0]-17*_aux[OC_MAXI(x-1,0)]+ + 114*_aux[x]+35*_aux[OC_MINI(x+1,c_w-1)]-9*_aux[OC_MINI(x+2,c_w-1)]+ + _aux[OC_MINI(x+3,c_w-1)]+64>>7,255); + } + for(;x<c_w-3;x++){ + tmp[x]=(unsigned char)OC_CLAMPI(0,4*_aux[x-2]-17*_aux[x-1]+ + 114*_aux[x]+35*_aux[x+1]-9*_aux[x+2]+_aux[x+3]+64>>7,255); + } + for(;x<c_w;x++){ + tmp[x]=(unsigned char)OC_CLAMPI(0,4*_aux[x-2]-17*_aux[x-1]+ + 114*_aux[x]+35*_aux[OC_MINI(x+1,c_w-1)]-9*_aux[OC_MINI(x+2,c_w-1)]+ + _aux[c_w-1]+64>>7,255); + } + tmp+=c_w; + _aux+=c_w; + } + switch(pli){ + case 1:{ + tmp-=c_sz; + /*Slide C_b up a quarter-pel. + This is the same filter used above, but in the other order.*/ + for(x=0;x<c_w;x++){ + for(y=0;y<OC_MINI(c_h,3);y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,tmp[0]- + 9*tmp[OC_MAXI(y-2,0)*c_w]+35*tmp[OC_MAXI(y-1,0)*c_w]+ + 114*tmp[y*c_w]-17*tmp[OC_MINI(y+1,c_h-1)*c_w]+ + 4*tmp[OC_MINI(y+2,c_h-1)*c_w]+64>>7,255); + } + for(;y<c_h-2;y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,tmp[(y-3)*c_w]- + 9*tmp[(y-2)*c_w]+35*tmp[(y-1)*c_w]+114*tmp[y*c_w]- + 17*tmp[(y+1)*c_w]+4*tmp[(y+2)*c_w]+64>>7,255); + } + for(;y<c_h;y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,tmp[(y-3)*c_w]- + 9*tmp[(y-2)*c_w]+35*tmp[(y-1)*c_w]+114*tmp[y*c_w]- + 17*tmp[OC_MINI(y+1,c_h-1)*c_w]+4*tmp[(c_h-1)*c_w]+64>>7,255); + } + _dst++; + tmp++; + } + _dst+=c_sz-c_w; + tmp-=c_w; + }break; + case 2:{ + tmp-=c_sz; + /*Slide C_r down a quarter-pel. + This is the same as the horizontal filter.*/ + for(x=0;x<c_w;x++){ + for(y=0;y<OC_MINI(c_h,2);y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,4*tmp[0]- + 17*tmp[OC_MAXI(y-1,0)*c_w]+114*tmp[y*c_w]+ + 35*tmp[OC_MINI(y+1,c_h-1)*c_w]-9*tmp[OC_MINI(y+2,c_h-1)*c_w]+ + tmp[OC_MINI(y+3,c_h-1)*c_w]+64>>7,255); + } + for(;y<c_h-3;y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,4*tmp[(y-2)*c_w]- + 17*tmp[(y-1)*c_w]+114*tmp[y*c_w]+35*tmp[(y+1)*c_w]- + 9*tmp[(y+2)*c_w]+tmp[(y+3)*c_w]+64>>7,255); + } + for(;y<c_h;y++){ + _dst[y*c_w]=(unsigned char)OC_CLAMPI(0,4*tmp[(y-2)*c_w]- + 17*tmp[(y-1)*c_w]+114*tmp[y*c_w]+35*tmp[OC_MINI(y+1,c_h-1)*c_w]- + 9*tmp[OC_MINI(y+2,c_h-1)*c_w]+tmp[(c_h-1)*c_w]+64>>7,255); + } + _dst++; + tmp++; + } + }break; + } + /*For actual interlaced material, this would have to be done separately on + each field, and the shift amounts would be different. + C_r moves down 1/8, C_b up 3/8 in the top field, and C_r moves down 3/8, + C_b up 1/8 in the bottom field. + The corresponding filters would be: + Down 1/8 (reverse order for up): [3 -11 125 15 -4 0]/128 + Down 3/8 (reverse order for up): [4 -19 98 56 -13 2]/128*/ + } +} + +/*422jpeg chroma samples are sited like: + Y---BR--Y-------Y---BR--Y------- + | | | | + | | | | + | | | | + Y---BR--Y-------Y---BR--Y------- + | | | | + | | | | + | | | | + Y---BR--Y-------Y---BR--Y------- + | | | | + | | | | + | | | | + Y---BR--Y-------Y---BR--Y------- + | | | | + | | | | + | | | | + + 411 chroma samples are sited like: + YBR-----Y-------Y-------Y------- + | | | | + | | | | + | | | | + YBR-----Y-------Y-------Y------- + | | | | + | | | | + | | | | + YBR-----Y-------Y-------Y------- + | | | | + | | | | + | | | | + YBR-----Y-------Y-------Y------- + | | | | + | | | | + | | | | + + We use a filter to resample at site locations one eighth pixel (at the source + chroma plane's horizontal resolution) and five eighths of a pixel to the + right.*/ +static void y4m_convert_411_422jpeg(unsigned char *_dst, + unsigned char *_aux){ + int c_w; + int dst_c_w; + int c_h; + int pli; + int y; + int x; + /*Skip past the luma data.*/ + _dst+=pic_w*pic_h; + /*Compute the size of each chroma plane.*/ + c_w=(pic_w+src_c_dec_h-1)/src_c_dec_h; + dst_c_w=(pic_w+dst_c_dec_h-1)/dst_c_dec_h; + c_h=(pic_h+dst_c_dec_v-1)/dst_c_dec_v; + for(pli=1;pli<3;pli++){ + for(y=0;y<c_h;y++){ + /*Filters: [1 110 18 -1]/128 and [-3 50 86 -5]/128, both derived from a + 4-tap Mitchell window.*/ + for(x=0;x<OC_MINI(c_w,1);x++){ + _dst[x<<1]=(unsigned char)OC_CLAMPI(0,111*_aux[0]+ + 18*_aux[OC_MINI(1,c_w-1)]-_aux[OC_MINI(2,c_w-1)]+64>>7,255); + _dst[x<<1|1]=(unsigned char)OC_CLAMPI(0,47*_aux[0]+ + 86*_aux[OC_MINI(1,c_w-1)]-5*_aux[OC_MINI(2,c_w-1)]+64>>7,255); + } + for(;x<c_w-2;x++){ + _dst[x<<1]=(unsigned char)OC_CLAMPI(0,_aux[x-1]+110*_aux[x]+ + 18*_aux[x+1]-_aux[x+2]+64>>7,255); + _dst[x<<1|1]=(unsigned char)OC_CLAMPI(0,-3*_aux[x-1]+50*_aux[x]+ + 86*_aux[x+1]-5*_aux[x+2]+64>>7,255); + } + for(;x<c_w;x++){ + _dst[x<<1]=(unsigned char)OC_CLAMPI(0,_aux[x-1]+110*_aux[x]+ + 18*_aux[OC_MINI(x+1,c_w-1)]-_aux[c_w-1]+64>>7,255); + if((x<<1|1)<dst_c_w){ + _dst[x<<1|1]=(unsigned char)OC_CLAMPI(0,-3*_aux[x-1]+50*_aux[x]+ + 86*_aux[OC_MINI(x+1,c_w-1)]-5*_aux[c_w-1]+64>>7,255); + } + } + _dst+=dst_c_w; + _aux+=c_w; + } + } +} + +/*The image is padded with empty chroma components at 4:2:0. + This costs about 17 bits a frame to code.*/ +static void y4m_convert_mono_420jpeg(unsigned char *_dst, + unsigned char *_aux){ + int c_sz; + _dst+=pic_w*pic_h; + c_sz=((pic_w+dst_c_dec_h-1)/dst_c_dec_h)*((pic_h+dst_c_dec_v-1)/dst_c_dec_v); + memset(_dst,128,c_sz*2); +} + +#if 0 +/*Right now just 444 to 420. + Not too hard to generalize.*/ +static void y4m_convert_4xxjpeg_42xjpeg(unsigned char *_dst, + unsigned char *_aux){ + unsigned char *tmp; + int c_w; + int c_h; + int pic_sz; + int tmp_sz; + int c_sz; + int pli; + int y; + int x; + /*Compute the size of each chroma plane.*/ + c_w=(pic_w+dst_c_dec_h-1)/dst_c_dec_h; + c_h=(pic_h+dst_c_dec_v-1)/dst_c_dec_v; + pic_sz=pic_w*pic_h; + tmp_sz=c_w*pic_h; + c_sz=c_w*c_h; + _dst+=pic_sz; + for(pli=1;pli<3;pli++){ + tmp=_aux+pic_sz; + /*In reality, the horizontal and vertical steps could be pipelined, for + less memory consumption and better cache performance, but we do them + separately for simplicity.*/ + /*First do horizontal filtering (convert to 4:2:2)*/ + /*Filter: [3 -17 78 78 -17 3]/128, derived from a 6-tap Lanczos window.*/ + for(y=0;y<pic_h;y++){ + for(x=0;x<OC_MINI(pic_w,2);x+=2){ + tmp[x>>1]=OC_CLAMPI(0,64*_aux[0]+78*_aux[OC_MINI(1,pic_w-1)]- + 17*_aux[OC_MINI(2,pic_w-1)]+3*_aux[OC_MINI(3,pic_w-1)]+64>>7,255); + } + for(;x<pic_w-3;x+=2){ + tmp[x>>1]=OC_CLAMPI(0,3*(_aux[x-2]+_aux[x+3])-17*(_aux[x-1]+_aux[x+2])+ + 78*(_aux[x]+_aux[x+1])+64>>7,255); + } + for(;x<pic_w;x+=2){ + tmp[x>>1]=OC_CLAMPI(0,3*(_aux[x-2]+_aux[pic_w-1])- + 17*(_aux[x-1]+_aux[OC_MINI(x+2,pic_w-1)])+ + 78*(_aux[x]+_aux[OC_MINI(x+1,pic_w-1)])+64>>7,255); + } + tmp+=c_w; + _aux+=pic_w; + } + _aux-=pic_sz; + tmp-=tmp_sz; + /*Now do the vertical filtering.*/ + for(x=0;x<c_w;x++){ + for(y=0;y<OC_MINI(pic_h,2);y+=2){ + _dst[(y>>1)*c_w]=OC_CLAMPI(0,64*tmp[0]+78*tmp[OC_MINI(1,pic_h-1)*c_w]- + 17*tmp[OC_MINI(2,pic_h-1)*c_w]+3*tmp[OC_MINI(3,pic_h-1)*c_w]+ + 64>>7,255); + } + for(;y<pic_h-3;y+=2){ + _dst[(y>>1)*c_w]=OC_CLAMPI(0,3*(tmp[(y-2)*c_w]+tmp[(y+3)*c_w])- + 17*(tmp[(y-1)*c_w]+tmp[(y+2)*c_w])+78*(tmp[y*c_w]+tmp[(y+1)*c_w])+ + 64>>7,255); + } + for(;y<pic_h;y+=2){ + _dst[(y>>1)*c_w]=OC_CLAMPI(0,3*(tmp[(y-2)*c_w]+tmp[(pic_h-1)*c_w])- + 17*(tmp[(y-1)*c_w]+tmp[OC_MINI(y+2,pic_h-1)*c_w])+ + 78*(tmp[y*c_w]+tmp[OC_MINI(y+1,pic_h-1)*c_w])+64>>7,255); + } + tmp++; + _dst++; + } + _dst-=c_w; + } +} +#endif + + +/*No conversion function needed.*/ +static void y4m_convert_null(unsigned char *_dst, + unsigned char *_aux){ +} + +static void id_file(char *f){ + FILE *test; + unsigned char buffer[80]; + int ret; + + /* open it, look for magic */ + + if(!strcmp(f,"-")){ + /* stdin */ + test=stdin; + }else{ + test=fopen(f,"rb"); + if(!test){ + fprintf(stderr,"Unable to open file %s.\n",f); + exit(1); + } + } + + ret=fread(buffer,1,4,test); + if(ret<4){ + fprintf(stderr,"EOF determining file type of file %s.\n",f); + exit(1); + } + + if(!memcmp(buffer,"RIFF",4)){ + /* possible WAV file */ + + if(audio){ + /* umm, we already have one */ + fprintf(stderr,"Multiple RIFF WAVE files specified on command line.\n"); + exit(1); + } + + /* Parse the rest of the header */ + + ret=fread(buffer,1,8,test); + if(ret<8)goto riff_err; + if(!memcmp(buffer+4,"WAVE",4)){ + + while(!feof(test)){ + ret=fread(buffer,1,4,test); + if(ret<4)goto riff_err; + if(!memcmp("fmt",buffer,3)){ + + /* OK, this is our audio specs chunk. Slurp it up. */ + + ret=fread(buffer,1,20,test); + if(ret<20)goto riff_err; + + if(memcmp(buffer+4,"\001\000",2)){ + fprintf(stderr,"The WAV file %s is in a compressed format; " + "can't read it.\n",f); + exit(1); + } + + audio=test; + audio_ch=buffer[6]+(buffer[7]<<8); + audio_hz=buffer[8]+(buffer[9]<<8)+ + (buffer[10]<<16)+(buffer[11]<<24); + + if(buffer[18]+(buffer[19]<<8)!=16){ + fprintf(stderr,"Can only read 16 bit WAV files for now.\n"); + exit(1); + } + + /* Now, align things to the beginning of the data */ + /* Look for 'dataxxxx' */ + while(!feof(test)){ + ret=fread(buffer,1,4,test); + if(ret<4)goto riff_err; + if(!memcmp("data",buffer,4)){ + /* We're there. Ignore the declared size for now. */ + ret=fread(buffer,1,4,test); + if(ret<4)goto riff_err; + + fprintf(stderr,"File %s is 16 bit %d channel %d Hz RIFF WAV audio.\n", + f,audio_ch,audio_hz); + + return; + } + } + } + } + } + + fprintf(stderr,"Couldn't find WAVE data in RIFF file %s.\n",f); + exit(1); + + } + if(!memcmp(buffer,"YUV4",4)){ + /* possible YUV2MPEG2 format file */ + /* read until newline, or 80 cols, whichever happens first */ + int i; + for(i=0;i<79;i++){ + ret=fread(buffer+i,1,1,test); + if(ret<1)goto yuv_err; + if(buffer[i]=='\n')break; + } + if(i==79){ + fprintf(stderr,"Error parsing %s header; not a YUV2MPEG2 file?\n",f); + } + buffer[i]='\0'; + + if(!memcmp(buffer,"MPEG",4)){ + + if(video){ + /* umm, we already have one */ + fprintf(stderr,"Multiple video files specified on command line.\n"); + exit(1); + } + + if(buffer[4]!='2'){ + fprintf(stderr,"Incorrect YUV input file version; YUV4MPEG2 required.\n"); + } + + ret=y4m_parse_tags((char *)buffer+5); + if(ret<0){ + fprintf(stderr,"Error parsing YUV4MPEG2 header in file %s.\n",f); + exit(1); + } + + if(interlace!='p'){ + fprintf(stderr,"Input video is interlaced; Theora handles only progressive scan\n"); + exit(1); + } + + if(strcmp(chroma_type,"420")==0||strcmp(chroma_type,"420jpeg")==0){ + src_c_dec_h=dst_c_dec_h=src_c_dec_v=dst_c_dec_v=2; + y4m_dst_buf_read_sz=pic_w*pic_h+2*((pic_w+1)/2)*((pic_h+1)/2); + y4m_aux_buf_sz=y4m_aux_buf_read_sz=0; + y4m_convert=y4m_convert_null; + } + else if(strcmp(chroma_type,"420mpeg2")==0){ + src_c_dec_h=dst_c_dec_h=src_c_dec_v=dst_c_dec_v=2; + y4m_dst_buf_read_sz=pic_w*pic_h; + /*Chroma filter required: read into the aux buf first.*/ + y4m_aux_buf_sz=y4m_aux_buf_read_sz=2*((pic_w+1)/2)*((pic_h+1)/2); + y4m_convert=y4m_convert_42xmpeg2_42xjpeg; + } + else if(strcmp(chroma_type,"420paldv")==0){ + src_c_dec_h=dst_c_dec_h=src_c_dec_v=dst_c_dec_v=2; + y4m_dst_buf_read_sz=pic_w*pic_h; + /*Chroma filter required: read into the aux buf first. + We need to make two filter passes, so we need some extra space in the + aux buffer.*/ + y4m_aux_buf_sz=3*((pic_w+1)/2)*((pic_h+1)/2); + y4m_aux_buf_read_sz=2*((pic_w+1)/2)*((pic_h+1)/2); + y4m_convert=y4m_convert_42xpaldv_42xjpeg; + } + else if(strcmp(chroma_type,"422")==0){ + src_c_dec_h=dst_c_dec_h=2; + src_c_dec_v=dst_c_dec_v=1; + y4m_dst_buf_read_sz=pic_w*pic_h; + /*Chroma filter required: read into the aux buf first.*/ + y4m_aux_buf_sz=y4m_aux_buf_read_sz=2*((pic_w+1)/2)*pic_h; + y4m_convert=y4m_convert_42xmpeg2_42xjpeg; + } + else if(strcmp(chroma_type,"411")==0){ + src_c_dec_h=4; + /*We don't want to introduce any additional sub-sampling, so we + promote 4:1:1 material to 4:2:2, as the closest format Theora can + handle.*/ + dst_c_dec_h=2; + src_c_dec_v=dst_c_dec_v=1; + y4m_dst_buf_read_sz=pic_w*pic_h; + /*Chroma filter required: read into the aux buf first.*/ + y4m_aux_buf_sz=y4m_aux_buf_read_sz=2*((pic_w+3)/4)*pic_h; + y4m_convert=y4m_convert_411_422jpeg; + } + else if(strcmp(chroma_type,"444")==0){ + src_c_dec_h=dst_c_dec_h=src_c_dec_v=dst_c_dec_v=1; + y4m_dst_buf_read_sz=pic_w*pic_h*3; + y4m_aux_buf_sz=y4m_aux_buf_read_sz=0; + y4m_convert=y4m_convert_null; + } + else if(strcmp(chroma_type,"444alpha")==0){ + src_c_dec_h=dst_c_dec_h=src_c_dec_v=dst_c_dec_v=1; + y4m_dst_buf_read_sz=pic_w*pic_h*3; + /*Read the extra alpha plane into the aux buf. + It will be discarded.*/ + y4m_aux_buf_sz=y4m_aux_buf_read_sz=pic_w*pic_h; + y4m_convert=y4m_convert_null; + } + else if(strcmp(chroma_type,"mono")==0){ + src_c_dec_h=src_c_dec_v=0; + dst_c_dec_h=dst_c_dec_v=2; + y4m_dst_buf_read_sz=pic_w*pic_h; + y4m_aux_buf_sz=y4m_aux_buf_read_sz=0; + y4m_convert=y4m_convert_mono_420jpeg; + } + else{ + fprintf(stderr,"Unknown chroma sampling type: %s\n",chroma_type); + exit(1); + } + /*The size of the final frame buffers is always computed from the + destination chroma decimation type.*/ + y4m_dst_buf_sz=pic_w*pic_h+2*((pic_w+dst_c_dec_h-1)/dst_c_dec_h)* + ((pic_h+dst_c_dec_v-1)/dst_c_dec_v); + + video=test; + + fprintf(stderr,"File %s is %dx%d %.02f fps %s video.\n", + f,pic_w,pic_h,(double)video_fps_n/video_fps_d,chroma_type); + + return; + } + } + fprintf(stderr,"Input file %s is neither a WAV nor YUV4MPEG2 file.\n",f); + exit(1); + + riff_err: + fprintf(stderr,"EOF parsing RIFF file %s.\n",f); + exit(1); + yuv_err: + fprintf(stderr,"EOF parsing YUV4MPEG2 file %s.\n",f); + exit(1); + +} + +int spinner=0; +char *spinascii="|/-\\"; +void spinnit(void){ + spinner++; + if(spinner==4)spinner=0; + fprintf(stderr,"\r%c",spinascii[spinner]); +} + +int fetch_and_process_audio(FILE *audio,ogg_page *audiopage, + ogg_stream_state *vo, + vorbis_dsp_state *vd, + vorbis_block *vb, + int audioflag){ + static ogg_int64_t samples_sofar=0; + ogg_packet op; + int i,j; + ogg_int64_t beginsample = audio_hz*begin_sec + audio_hz*begin_usec*.000001; + ogg_int64_t endsample = audio_hz*end_sec + audio_hz*end_usec*.000001; + + while(audio && !audioflag){ + /* process any audio already buffered */ + spinnit(); + if(ogg_stream_pageout(vo,audiopage)>0) return 1; + if(ogg_stream_eos(vo))return 0; + + { + /* read and process more audio */ + signed char readbuffer[4096]; + signed char *readptr=readbuffer; + int toread=4096/2/audio_ch; + int bytesread=fread(readbuffer,1,toread*2*audio_ch,audio); + int sampread=bytesread/2/audio_ch; + float **vorbis_buffer; + int count=0; + + if(bytesread<=0 || + (samples_sofar>=endsample && endsample>0)){ + /* end of file. this can be done implicitly, but it's + easier to see here in non-clever fashion. Tell the + library we're at end of stream so that it can handle the + last frame and mark end of stream in the output properly */ + vorbis_analysis_wrote(vd,0); + }else{ + if(samples_sofar < beginsample){ + if(samples_sofar+sampread > beginsample){ + readptr += (beginsample-samples_sofar)*2*audio_ch; + sampread += samples_sofar-beginsample; + samples_sofar = sampread+beginsample; + }else{ + samples_sofar += sampread; + sampread = 0; + } + }else{ + samples_sofar += sampread; + } + + if(samples_sofar > endsample && endsample > 0) + sampread-= (samples_sofar - endsample); + + if(sampread>0){ + + vorbis_buffer=vorbis_analysis_buffer(vd,sampread); + /* uninterleave samples */ + for(i=0;i<sampread;i++){ + for(j=0;j<audio_ch;j++){ + vorbis_buffer[j][i]=((readptr[count+1]<<8)| + (0x00ff&(int)readptr[count]))/32768.f; + count+=2; + } + } + + vorbis_analysis_wrote(vd,sampread); + } + } + + while(vorbis_analysis_blockout(vd,vb)==1){ + + /* analysis, assume we want to use bitrate management */ + vorbis_analysis(vb,NULL); + vorbis_bitrate_addblock(vb); + + /* weld packets into the bitstream */ + while(vorbis_bitrate_flushpacket(vd,&op)) + ogg_stream_packetin(vo,&op); + + } + } + } + + return audioflag; +} + +static int frame_state=-1; +static ogg_int64_t frames=0; +static unsigned char *yuvframe[3]; +static th_ycbcr_buffer ycbcr; + +int fetch_and_process_video_packet(FILE *video,FILE *twopass_file,int passno, + th_enc_ctx *td,ogg_packet *op){ + int ret; + int pic_sz; + int frame_c_w; + int frame_c_h; + int c_w; + int c_h; + int c_sz; + ogg_int64_t beginframe; + ogg_int64_t endframe; + spinnit(); + beginframe=(video_fps_n*begin_sec+video_fps_n*begin_usec*.000001)/video_fps_d; + endframe=(video_fps_n*end_sec+video_fps_n*end_usec*.000001)/video_fps_d; + if(frame_state==-1){ + /* initialize the double frame buffer */ + yuvframe[0]=(unsigned char *)malloc(y4m_dst_buf_sz); + yuvframe[1]=(unsigned char *)malloc(y4m_dst_buf_sz); + yuvframe[2]=(unsigned char *)malloc(y4m_aux_buf_sz); + frame_state=0; + } + pic_sz=pic_w*pic_h; + frame_c_w=frame_w/dst_c_dec_h; + frame_c_h=frame_h/dst_c_dec_v; + c_w=(pic_w+dst_c_dec_h-1)/dst_c_dec_h; + c_h=(pic_h+dst_c_dec_v-1)/dst_c_dec_v; + c_sz=c_w*c_h; + /* read and process more video */ + /* video strategy reads one frame ahead so we know when we're + at end of stream and can mark last video frame as such + (vorbis audio has to flush one frame past last video frame + due to overlap and thus doesn't need this extra work */ + + /* have two frame buffers full (if possible) before + proceeding. after first pass and until eos, one will + always be full when we get here */ + for(;frame_state<2 && (frames<endframe || endframe<0);){ + char c,frame[6]; + int ret=fread(frame,1,6,video); + /* match and skip the frame header */ + if(ret<6)break; + if(memcmp(frame,"FRAME",5)){ + fprintf(stderr,"Loss of framing in YUV input data\n"); + exit(1); + } + if(frame[5]!='\n'){ + int j; + for(j=0;j<79;j++) + if(fread(&c,1,1,video)&&c=='\n')break; + if(j==79){ + fprintf(stderr,"Error parsing YUV frame header\n"); + exit(1); + } + } + /*Read the frame data that needs no conversion.*/ + if(fread(yuvframe[frame_state],1,y4m_dst_buf_read_sz,video)!= + y4m_dst_buf_read_sz){ + fprintf(stderr,"Error reading YUV frame data.\n"); + exit(1); + } + /*Read the frame data that does need conversion.*/ + if(fread(yuvframe[2],1,y4m_aux_buf_read_sz,video)!=y4m_aux_buf_read_sz){ + fprintf(stderr,"Error reading YUV frame data.\n"); + exit(1); + } + /*Now convert the just read frame.*/ + (*y4m_convert)(yuvframe[frame_state],yuvframe[2]); + frames++; + if(frames>=beginframe) + frame_state++; + } + /* check to see if there are dupes to flush */ + if(th_encode_packetout(td,frame_state<1,op)>0)return 1; + if(frame_state<1){ + /* can't get here unless YUV4MPEG stream has no video */ + fprintf(stderr,"Video input contains no frames.\n"); + exit(1); + } + /* Theora is a one-frame-in,one-frame-out system; submit a frame + for compression and pull out the packet */ + /* in two-pass mode's second pass, we need to submit first-pass data */ + if(passno==2){ + for(;;){ + static unsigned char buffer[80]; + static int buf_pos; + int bytes; + /*Ask the encoder how many bytes it would like.*/ + bytes=th_encode_ctl(td,TH_ENCCTL_2PASS_IN,NULL,0); + if(bytes<0){ + fprintf(stderr,"Error submitting pass data in second pass.\n"); + exit(1); + } + /*If it's got enough, stop.*/ + if(bytes==0)break; + /*Read in some more bytes, if necessary.*/ + if(bytes>80-buf_pos)bytes=80-buf_pos; + if(bytes>0&&fread(buffer+buf_pos,1,bytes,twopass_file)<bytes){ + fprintf(stderr,"Could not read frame data from two-pass data file!\n"); + exit(1); + } + /*And pass them off.*/ + ret=th_encode_ctl(td,TH_ENCCTL_2PASS_IN,buffer,bytes); + if(ret<0){ + fprintf(stderr,"Error submitting pass data in second pass.\n"); + exit(1); + } + /*If the encoder consumed the whole buffer, reset it.*/ + if(ret>=bytes)buf_pos=0; + /*Otherwise remember how much it used.*/ + else buf_pos+=ret; + } + } + /*We submit the buffer to the library as if it were padded, but we do not + actually allocate space for the padding. + This is okay, because with the 1.0 API the library will never read data from the padded + region.*/ + ycbcr[0].width=frame_w; + ycbcr[0].height=frame_h; + ycbcr[0].stride=pic_w; + ycbcr[0].data=yuvframe[0]-pic_x-pic_y*pic_w; + ycbcr[1].width=frame_c_w; + ycbcr[1].height=frame_c_h; + ycbcr[1].stride=c_w; + ycbcr[1].data=yuvframe[0]+pic_sz-(pic_x/dst_c_dec_h)-(pic_y/dst_c_dec_v)*c_w; + ycbcr[2].width=frame_c_w; + ycbcr[2].height=frame_c_h; + ycbcr[2].stride=c_w; + ycbcr[2].data=ycbcr[1].data+c_sz; + th_encode_ycbcr_in(td,ycbcr); + { + unsigned char *temp=yuvframe[0]; + yuvframe[0]=yuvframe[1]; + yuvframe[1]=temp; + frame_state--; + } + /* in two-pass mode's first pass we need to extract and save the pass data */ + if(passno==1){ + unsigned char *buffer; + int bytes = th_encode_ctl(td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer)); + if(bytes<0){ + fprintf(stderr,"Could not read two-pass data from encoder.\n"); + exit(1); + } + if(fwrite(buffer,1,bytes,twopass_file)<bytes){ + fprintf(stderr,"Unable to write to two-pass data file.\n"); + exit(1); + } + fflush(twopass_file); + } + /* if there was only one frame, it's the last in the stream */ + ret = th_encode_packetout(td,frame_state<1,op); + if(passno==1 && frame_state<1){ + /* need to read the final (summary) packet */ + unsigned char *buffer; + int bytes = th_encode_ctl(td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer)); + if(bytes<0){ + fprintf(stderr,"Could not read two-pass summary data from encoder.\n"); + exit(1); + } + if(fseek(twopass_file,0,SEEK_SET)<0){ + fprintf(stderr,"Unable to seek in two-pass data file.\n"); + exit(1); + } + if(fwrite(buffer,1,bytes,twopass_file)<bytes){ + fprintf(stderr,"Unable to write to two-pass data file.\n"); + exit(1); + } + fflush(twopass_file); + } + return ret; +} + + +int fetch_and_process_video(FILE *video,ogg_page *videopage, + ogg_stream_state *to,th_enc_ctx *td,FILE *twopass_file,int passno, + int videoflag){ + ogg_packet op; + int ret; + /* is there a video page flushed? If not, work until there is. */ + while(!videoflag){ + if(ogg_stream_pageout(to,videopage)>0) return 1; + if(ogg_stream_eos(to)) return 0; + ret=fetch_and_process_video_packet(video,twopass_file,passno,td,&op); + if(ret<=0)return 0; + ogg_stream_packetin(to,&op); + } + return videoflag; +} + +static int ilog(unsigned _v){ + int ret; + for(ret=0;_v;ret++)_v>>=1; + return ret; +} + +int main(int argc,char *argv[]){ + int c,long_option_index,ret; + + ogg_stream_state to; /* take physical pages, weld into a logical + stream of packets */ + ogg_stream_state vo; /* take physical pages, weld into a logical + stream of packets */ + ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */ + ogg_packet op; /* one raw packet of data for decode */ + + th_enc_ctx *td; + th_info ti; + th_comment tc; + + vorbis_info vi; /* struct that stores all the static vorbis bitstream + settings */ + vorbis_comment vc; /* struct that stores all the user comments */ + + vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ + vorbis_block vb; /* local working space for packet->PCM decode */ + + int speed=-1; + int audioflag=0; + int videoflag=0; + int akbps=0; + int vkbps=0; + int soft_target=0; + + ogg_int64_t audio_bytesout=0; + ogg_int64_t video_bytesout=0; + double timebase; + + FILE *outfile = stdout; + + FILE *twopass_file = NULL; + fpos_t video_rewind_pos; + int twopass=0; + int passno; + +#ifdef _WIN32 /* We need to set stdin/stdout to binary mode. Damn windows. */ + /* if we were reading/writing a file, it would also need to in + binary mode, eg, fopen("file.wav","wb"); */ + /* Beware the evil ifdef. We avoid these where we can, but this one we + cannot. Don't add any more, you'll probably go to hell if you do. */ + _setmode( _fileno( stdin ), _O_BINARY ); + _setmode( _fileno( stdout ), _O_BINARY ); +#endif + + while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){ + switch(c){ + case 'o': + outfile=fopen(optarg,"wb"); + if(outfile==NULL){ + fprintf(stderr,"Unable to open output file '%s'\n", optarg); + exit(1); + } + break;; + + case 'a': + audio_q=(float)(atof(optarg)*.099); + if(audio_q<-.1 || audio_q>1){ + fprintf(stderr,"Illegal audio quality (choose -1 through 10)\n"); + exit(1); + } + audio_r=-1; + break; + + case 'v': + video_q=(int)rint(6.3*atof(optarg)); + if(video_q<0 || video_q>63){ + fprintf(stderr,"Illegal video quality (choose 0 through 10)\n"); + exit(1); + } + break; + + case 'A': + audio_r=(int)(atof(optarg)*1000); + if(audio_q<0){ + fprintf(stderr,"Illegal audio quality (choose > 0 please)\n"); + exit(1); + } + audio_q=-99; + break; + + case 'V': + video_r=(int)rint(atof(optarg)*1000); + if(video_r<1){ + fprintf(stderr,"Illegal video bitrate (choose > 0 please)\n"); + exit(1); + } + break; + + case '\1': + soft_target=1; + break; + + case 's': + video_par_n=(int)rint(atof(optarg)); + break; + + case 'S': + video_par_d=(int)rint(atof(optarg)); + break; + + case 'f': + video_fps_n=(int)rint(atof(optarg)); + break; + + case 'F': + video_fps_d=(int)rint(atof(optarg)); + break; + + case 'c': + vp3_compatible=1; + break; + + case 'k': + keyframe_frequency=rint(atof(optarg)); + if(keyframe_frequency<1 || keyframe_frequency>2147483647){ + fprintf(stderr,"Illegal keyframe frequency\n"); + exit(1); + } + break; + + case 'd': + buf_delay=atoi(optarg); + if(buf_delay<=0){ + fprintf(stderr,"Illegal buffer delay\n"); + exit(1); + } + break; + + case 'z': + speed=atoi(optarg); + if(speed<0){ + fprintf(stderr,"Illegal speed level\n"); + exit(1); + } + break; + + case 'b': + { + char *pos=strchr(optarg,':'); + begin_sec=atol(optarg); + if(pos){ + char *pos2=strchr(++pos,':'); + begin_sec*=60; + begin_sec+=atol(pos); + if(pos2){ + pos2++; + begin_sec*=60; + begin_sec+=atol(pos2); + pos=pos2; + } + }else + pos=optarg; + pos=strchr(pos,'.'); + if(pos){ + int digits = strlen(++pos); + begin_usec=atol(pos); + while(digits++ < 6) + begin_usec*=10; + } + } + break; + case 'e': + { + char *pos=strchr(optarg,':'); + end_sec=atol(optarg); + if(pos){ + char *pos2=strchr(++pos,':'); + end_sec*=60; + end_sec+=atol(pos); + if(pos2){ + pos2++; + end_sec*=60; + end_sec+=atol(pos2); + pos=pos2; + } + }else + pos=optarg; + pos=strchr(pos,'.'); + if(pos){ + int digits = strlen(++pos); + end_usec=atol(pos); + while(digits++ < 6) + end_usec*=10; + } + } + break; + case '\2': + twopass=3; /* perform both passes */ + twopass_file=tmpfile(); + if(!twopass_file){ + fprintf(stderr,"Unable to open temporary file for twopass data\n"); + exit(1); + } + break; + case '\3': + twopass=1; /* perform first pass */ + twopass_file=fopen(optarg,"wb"); + if(!twopass_file){ + fprintf(stderr,"Unable to open \'%s\' for twopass data\n",optarg); + exit(1); + } + break; + case '\4': + twopass=2; /* perform second pass */ + twopass_file=fopen(optarg,"rb"); + if(!twopass_file){ + fprintf(stderr,"Unable to open twopass data file \'%s\'",optarg); + exit(1); + } + break; + + default: + usage(); + } + } + + if(soft_target){ + if(video_r<=0){ + fprintf(stderr,"Soft rate target (--soft-target) requested without a bitrate (-V).\n"); + exit(1); + } + if(video_q==-1) + video_q=0; + }else{ + if(video_q==-1){ + if(video_r>0) + video_q=0; + else + video_q=48; + } + } + + if(keyframe_frequency<=0){ + /*Use a default keyframe frequency of 64 for 1-pass (streaming) mode, and + 256 for two-pass mode.*/ + keyframe_frequency=twopass?256:64; + } + + while(optind<argc){ + /* assume that anything following the options must be a filename */ + id_file(argv[optind]); + optind++; + } + + if(twopass==3){ + /* verify that the input is seekable! */ + if(video){ + if(fseek(video,0,SEEK_CUR)){ + fprintf(stderr,"--two-pass (automatic two-pass) requires the video input\n" + "to be seekable. For non-seekable input, encoder_example\n" + "must be run twice, first with the --first-pass option, then\n" + "with the --second-pass option.\n\n"); + exit(1); + } + if(fgetpos(video,&video_rewind_pos)<0){ + fprintf(stderr,"Unable to determine start position of video data.\n"); + exit(1); + } + } + } + + /* Set up Ogg output stream */ + srand(time(NULL)); + ogg_stream_init(&to,rand()); /* oops, add one ot the above */ + + /* initialize Vorbis assuming we have audio to compress. */ + if(audio && twopass!=1){ + ogg_stream_init(&vo,rand()); + vorbis_info_init(&vi); + if(audio_q>-99) + ret = vorbis_encode_init_vbr(&vi,audio_ch,audio_hz,audio_q); + else + ret = vorbis_encode_init(&vi,audio_ch,audio_hz,-1, + (int)(64870*(ogg_int64_t)audio_r>>16),-1); + if(ret){ + fprintf(stderr,"The Vorbis encoder could not set up a mode according to\n" + "the requested quality or bitrate.\n\n"); + exit(1); + } + + vorbis_comment_init(&vc); + vorbis_analysis_init(&vd,&vi); + vorbis_block_init(&vd,&vb); + } + + for(passno=(twopass==3?1:twopass);passno<=(twopass==3?2:twopass);passno++){ + /* Set up Theora encoder */ + if(!video){ + fprintf(stderr,"No video files submitted for compression?\n"); + exit(1); + } + /* Theora has a divisible-by-sixteen restriction for the encoded frame size */ + /* scale the picture size up to the nearest /16 and calculate offsets */ + frame_w=pic_w+15&~0xF; + frame_h=pic_h+15&~0xF; + /*Force the offsets to be even so that chroma samples line up like we + expect.*/ + pic_x=frame_w-pic_w>>1&~1; + pic_y=frame_h-pic_h>>1&~1; + th_info_init(&ti); + ti.frame_width=frame_w; + ti.frame_height=frame_h; + ti.pic_width=pic_w; + ti.pic_height=pic_h; + ti.pic_x=pic_x; + ti.pic_y=pic_y; + ti.fps_numerator=video_fps_n; + ti.fps_denominator=video_fps_d; + ti.aspect_numerator=video_par_n; + ti.aspect_denominator=video_par_d; + ti.colorspace=TH_CS_UNSPECIFIED; + /*Account for the Ogg page overhead. + This is 1 byte per 255 for lacing values, plus 26 bytes per 4096 bytes for + the page header, plus approximately 1/2 byte per packet (not accounted for + here).*/ + ti.target_bitrate=(int)(64870*(ogg_int64_t)video_r>>16); + ti.quality=video_q; + ti.keyframe_granule_shift=ilog(keyframe_frequency-1); + if(dst_c_dec_h==2){ + if(dst_c_dec_v==2)ti.pixel_fmt=TH_PF_420; + else ti.pixel_fmt=TH_PF_422; + } + else ti.pixel_fmt=TH_PF_444; + td=th_encode_alloc(&ti); + th_info_clear(&ti); + /* setting just the granule shift only allows power-of-two keyframe + spacing. Set the actual requested spacing. */ + ret=th_encode_ctl(td,TH_ENCCTL_SET_KEYFRAME_FREQUENCY_FORCE, + &keyframe_frequency,sizeof(keyframe_frequency-1)); + if(ret<0){ + fprintf(stderr,"Could not set keyframe interval to %d.\n",(int)keyframe_frequency); + } + if(vp3_compatible){ + ret=th_encode_ctl(td,TH_ENCCTL_SET_VP3_COMPATIBLE,&vp3_compatible, + sizeof(vp3_compatible)); + if(ret<0||!vp3_compatible){ + fprintf(stderr,"Could not enable strict VP3 compatibility.\n"); + if(ret>=0){ + fprintf(stderr,"Ensure your source format is supported by VP3.\n"); + fprintf(stderr, + "(4:2:0 pixel format, width and height multiples of 16).\n"); + } + } + } + if(soft_target){ + /* reverse the rate control flags to favor a 'long time' strategy */ + int arg = TH_RATECTL_CAP_UNDERFLOW; + ret=th_encode_ctl(td,TH_ENCCTL_SET_RATE_FLAGS,&arg,sizeof(arg)); + if(ret<0) + fprintf(stderr,"Could not set encoder flags for --soft-target\n"); + /* Default buffer control is overridden on two-pass */ + if(!twopass&&buf_delay<0){ + if((keyframe_frequency*7>>1) > 5*video_fps_n/video_fps_d) + arg=keyframe_frequency*7>>1; + else + arg=5*video_fps_n/video_fps_d; + ret=th_encode_ctl(td,TH_ENCCTL_SET_RATE_BUFFER,&arg,sizeof(arg)); + if(ret<0) + fprintf(stderr,"Could not set rate control buffer for --soft-target\n"); + } + } + /* set up two-pass if needed */ + if(passno==1){ + unsigned char *buffer; + int bytes; + bytes=th_encode_ctl(td,TH_ENCCTL_2PASS_OUT,&buffer,sizeof(buffer)); + if(bytes<0){ + fprintf(stderr,"Could not set up the first pass of two-pass mode.\n"); + fprintf(stderr,"Did you remember to specify an estimated bitrate?\n"); + exit(1); + } + /*Perform a seek test to ensure we can overwrite this placeholder data at + the end; this is better than letting the user sit through a whole + encode only to find out their pass 1 file is useless at the end.*/ + if(fseek(twopass_file,0,SEEK_SET)<0){ + fprintf(stderr,"Unable to seek in two-pass data file.\n"); + exit(1); + } + if(fwrite(buffer,1,bytes,twopass_file)<bytes){ + fprintf(stderr,"Unable to write to two-pass data file.\n"); + exit(1); + } + fflush(twopass_file); + } + if(passno==2){ + /*Enable the second pass here. + We make this call just to set the encoder into 2-pass mode, because + by default enabling two-pass sets the buffer delay to the whole file + (because there's no way to explicitly request that behavior). + If we waited until we were actually encoding, it would overwite our + settings.*/ + if(th_encode_ctl(td,TH_ENCCTL_2PASS_IN,NULL,0)<0){ + fprintf(stderr,"Could not set up the second pass of two-pass mode.\n"); + exit(1); + } + if(twopass==3){ + /* 'automatic' second pass */ + if(fsetpos(video,&video_rewind_pos)<0){ + fprintf(stderr,"Could not rewind video input file for second pass!\n"); + exit(1); + } + if(fseek(twopass_file,0,SEEK_SET)<0){ + fprintf(stderr,"Unable to seek in two-pass data file.\n"); + exit(1); + } + frame_state=0; + frames=0; + } + } + /*Now we can set the buffer delay if the user requested a non-default one + (this has to be done after two-pass is enabled).*/ + if(passno!=1&&buf_delay>=0){ + ret=th_encode_ctl(td,TH_ENCCTL_SET_RATE_BUFFER, + &buf_delay,sizeof(buf_delay)); + if(ret<0){ + fprintf(stderr,"Warning: could not set desired buffer delay.\n"); + } + } + /*Speed should also be set after the current encoder mode is established, + since the available speed levels may change depending.*/ + if(speed>=0){ + int speed_max; + int ret; + ret=th_encode_ctl(td,TH_ENCCTL_GET_SPLEVEL_MAX, + &speed_max,sizeof(speed_max)); + if(ret<0){ + fprintf(stderr,"Warning: could not determine maximum speed level.\n"); + speed_max=0; + } + ret=th_encode_ctl(td,TH_ENCCTL_SET_SPLEVEL,&speed,sizeof(speed)); + if(ret<0){ + fprintf(stderr,"Warning: could not set speed level to %i of %i\n", + speed,speed_max); + if(speed>speed_max){ + fprintf(stderr,"Setting it to %i instead\n",speed_max); + } + ret=th_encode_ctl(td,TH_ENCCTL_SET_SPLEVEL, + &speed_max,sizeof(speed_max)); + if(ret<0){ + fprintf(stderr,"Warning: could not set speed level to %i of %i\n", + speed_max,speed_max); + } + } + } + /* write the bitstream header packets with proper page interleave */ + th_comment_init(&tc); + /* first packet will get its own page automatically */ + if(th_encode_flushheader(td,&tc,&op)<=0){ + fprintf(stderr,"Internal Theora library error.\n"); + exit(1); + } + if(passno!=1){ + ogg_stream_packetin(&to,&op); + if(ogg_stream_pageout(&to,&og)!=1){ + fprintf(stderr,"Internal Ogg library error.\n"); + exit(1); + } + fwrite(og.header,1,og.header_len,outfile); + fwrite(og.body,1,og.body_len,outfile); + } + /* create the remaining theora headers */ + for(;;){ + ret=th_encode_flushheader(td,&tc,&op); + if(ret<0){ + fprintf(stderr,"Internal Theora library error.\n"); + exit(1); + } + else if(!ret)break; + if(passno!=1)ogg_stream_packetin(&to,&op); + } + if(audio && passno!=1){ + ogg_packet header; + ogg_packet header_comm; + ogg_packet header_code; + vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code); + ogg_stream_packetin(&vo,&header); /* automatically placed in its own + page */ + if(ogg_stream_pageout(&vo,&og)!=1){ + fprintf(stderr,"Internal Ogg library error.\n"); + exit(1); + } + fwrite(og.header,1,og.header_len,outfile); + fwrite(og.body,1,og.body_len,outfile); + /* remaining vorbis header packets */ + ogg_stream_packetin(&vo,&header_comm); + ogg_stream_packetin(&vo,&header_code); + } + /* Flush the rest of our headers. This ensures + the actual data in each stream will start + on a new page, as per spec. */ + if(passno!=1){ + for(;;){ + int result = ogg_stream_flush(&to,&og); + if(result<0){ + /* can't get here */ + fprintf(stderr,"Internal Ogg library error.\n"); + exit(1); + } + if(result==0)break; + fwrite(og.header,1,og.header_len,outfile); + fwrite(og.body,1,og.body_len,outfile); + } + } + if(audio && passno!=1){ + for(;;){ + int result=ogg_stream_flush(&vo,&og); + if(result<0){ + /* can't get here */ + fprintf(stderr,"Internal Ogg library error.\n"); + exit(1); + } + if(result==0)break; + fwrite(og.header,1,og.header_len,outfile); + fwrite(og.body,1,og.body_len,outfile); + } + } + /* setup complete. Raw processing loop */ + switch(passno){ + case 0: case 2: + fprintf(stderr,"\rCompressing.... \n"); + break; + case 1: + fprintf(stderr,"\rScanning first pass.... \n"); + break; + } + for(;;){ + int audio_or_video=-1; + if(passno==1){ + ogg_packet op; + int ret=fetch_and_process_video_packet(video,twopass_file,passno,td,&op); + if(ret<0)break; + if(op.e_o_s)break; /* end of stream */ + timebase=th_granule_time(td,op.granulepos); + audio_or_video=1; + }else{ + double audiotime; + double videotime; + ogg_page audiopage; + ogg_page videopage; + /* is there an audio page flushed? If not, fetch one if possible */ + audioflag=fetch_and_process_audio(audio,&audiopage,&vo,&vd,&vb,audioflag); + /* is there a video page flushed? If not, fetch one if possible */ + videoflag=fetch_and_process_video(video,&videopage,&to,td,twopass_file,passno,videoflag); + /* no pages of either? Must be end of stream. */ + if(!audioflag && !videoflag)break; + /* which is earlier; the end of the audio page or the end of the + video page? Flush the earlier to stream */ + audiotime= + audioflag?vorbis_granule_time(&vd,ogg_page_granulepos(&audiopage)):-1; + videotime= + videoflag?th_granule_time(td,ogg_page_granulepos(&videopage)):-1; + if(!audioflag){ + audio_or_video=1; + } else if(!videoflag) { + audio_or_video=0; + } else { + if(audiotime<videotime) + audio_or_video=0; + else + audio_or_video=1; + } + if(audio_or_video==1){ + /* flush a video page */ + video_bytesout+=fwrite(videopage.header,1,videopage.header_len,outfile); + video_bytesout+=fwrite(videopage.body,1,videopage.body_len,outfile); + videoflag=0; + timebase=videotime; + }else{ + /* flush an audio page */ + audio_bytesout+=fwrite(audiopage.header,1,audiopage.header_len,outfile); + audio_bytesout+=fwrite(audiopage.body,1,audiopage.body_len,outfile); + audioflag=0; + timebase=audiotime; + } + } + if(timebase > 0){ + int hundredths=(int)(timebase*100-(long)timebase*100); + int seconds=(long)timebase%60; + int minutes=((long)timebase/60)%60; + int hours=(long)timebase/3600; + if(audio_or_video)vkbps=(int)rint(video_bytesout*8./timebase*.001); + else akbps=(int)rint(audio_bytesout*8./timebase*.001); + fprintf(stderr, + "\r %d:%02d:%02d.%02d audio: %dkbps video: %dkbps ", + hours,minutes,seconds,hundredths,akbps,vkbps); + } + } + if(video)th_encode_free(td); + } + + /* clear out state */ + if(audio && twopass!=1){ + ogg_stream_clear(&vo); + vorbis_block_clear(&vb); + vorbis_dsp_clear(&vd); + vorbis_comment_clear(&vc); + vorbis_info_clear(&vi); + if(audio!=stdin)fclose(audio); + } + if(video){ + ogg_stream_clear(&to); + th_comment_clear(&tc); + if(video!=stdin)fclose(video); + } + + if(outfile && outfile!=stdout)fclose(outfile); + if(twopass_file)fclose(twopass_file); + + fprintf(stderr,"\r \ndone.\n\n"); + + return(0); + +} diff --git a/examples/getopt.c b/examples/getopt.c new file mode 100644 index 0000000..9bafa45 --- /dev/null +++ b/examples/getopt.c @@ -0,0 +1,1055 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to drepper@gnu.org + before changing it! + Copyright (C) 1987,88,89,90,91,92,93,94,95,96,98,99,2000,2001 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library 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. + + The GNU C Library 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 the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. + Ditto for AIX 3.2 and <stdlib.h>. */ +#ifndef _NO_PROTO +# define _NO_PROTO +#endif + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +# ifndef const +# define const +# endif +#endif + +#include <stdio.h> + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +# include <gnu-versions.h> +# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +# define ELIDE_CODE +# endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +# include <stdlib.h> +# include <unistd.h> +#endif /* GNU C library. */ + +#ifdef VMS +# include <unixlib.h> +# if HAVE_STRING_H - 0 +# include <string.h> +# endif +#endif + +#ifndef _ +/* This is for other GNU distributions with internationalized messages. */ +# if defined HAVE_LIBINTL_H || defined _LIBC +# include <libintl.h> +# ifndef _ +# define _(msgid) gettext (msgid) +# endif +# else +# define _(msgid) (msgid) +# endif +#endif + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int optind = 1; + +/* Formerly, initialization of getopt depended on optind==0, which + causes problems with re-calling getopt as programs generally don't + know that. */ + +int __getopt_initialized; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return -1 with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +# include <string.h> +# define my_index strchr +#else + +# if HAVE_STRING_H +# include <string.h> +# else +# include <strings.h> +# endif + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +#ifndef getenv +extern char *getenv (); +#endif + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +# if (!defined __STDC__ || !__STDC__) && !defined strlen +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +# endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +#ifdef _LIBC +/* Stored original parameters. + XXX This is no good solution. We should rather copy the args so + that we can compare them later. But we must not use malloc(3). */ +extern int __libc_argc; +extern char **__libc_argv; + +/* Bash 2.0 gives us an environment variable containing flags + indicating ARGV elements that should not be considered arguments. */ + +# ifdef USE_NONOPTION_FLAGS +/* Defined in getopt_init.c */ +extern char *__getopt_nonoption_flags; + +static int nonoption_flags_max_len; +static int nonoption_flags_len; +# endif + +# ifdef USE_NONOPTION_FLAGS +# define SWAP_FLAGS(ch1, ch2) \ + if (nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ + } +# else +# define SWAP_FLAGS(ch1, ch2) +# endif +#else /* !_LIBC */ +# define SWAP_FLAGS(ch1, ch2) +#endif /* _LIBC */ + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +#if defined __STDC__ && __STDC__ +static void exchange (char **); +#endif + +static void +exchange (argv) + char **argv; +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + /* First make sure the handling of the `__getopt_nonoption_flags' + string can work normally. Our top argument must be in the range + of the string. */ + if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) + { + /* We must extend the array. The user plays games with us and + presents new arguments. */ + char *new_str = malloc (top + 1); + if (new_str == NULL) + nonoption_flags_len = nonoption_flags_max_len = 0; + else + { + memset (__mempcpy (new_str, __getopt_nonoption_flags, + nonoption_flags_max_len), + '\0', top + 1 - nonoption_flags_max_len); + nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } + } +#endif + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +#if defined __STDC__ && __STDC__ +static const char *_getopt_initialize (int, char *const *, const char *); +#endif +static const char * +_getopt_initialize (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + if (posixly_correct == NULL + && argc == __libc_argc && argv == __libc_argv) + { + if (nonoption_flags_max_len == 0) + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = nonoption_flags_max_len = strlen (orig_str); + if (nonoption_flags_max_len < argc) + nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + (char *) malloc (nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + nonoption_flags_max_len = -1; + else + memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), + '\0', nonoption_flags_max_len - len); + } + } + nonoption_flags_len = nonoption_flags_max_len; + } + else + nonoption_flags_len = 0; +#endif + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns -1. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + int print_errors = opterr; + if (optstring[0] == ':') + print_errors = 0; + + if (argc < 1) + return -1; + + optarg = NULL; + + if (optind == 0 || !__getopt_initialized) + { + if (optind == 0) + optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize (argc, argv, optstring); + __getopt_initialized = 1; + } + + /* Test whether ARGV[optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#if defined _LIBC && defined USE_NONOPTION_FLAGS +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ + || (optind < nonoption_flags_len \ + && __getopt_nonoption_flags[optind] == '1')) +#else +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') +#endif + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (last_nonopt > optind) + last_nonopt = optind; + if (first_nonopt > optind) + first_nonopt = optind; + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc && NONOPTION_P) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) + { + if (ordering == REQUIRE_ORDER) + return -1; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) + == (unsigned int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else if (long_only + || pfound->has_arg != p->has_arg + || pfound->flag != p->flag + || pfound->val != p->val) + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (print_errors) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (print_errors) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[optind - 1][0], pfound->name); + } + + nextchar += strlen (nextchar); + + optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (print_errors) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (print_errors) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (print_errors) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], c); + } + optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (print_errors) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + + /* optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (print_errors) + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); + + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (print_errors) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (print_errors) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == -1) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/examples/getopt.h b/examples/getopt.h new file mode 100644 index 0000000..a1b8dd6 --- /dev/null +++ b/examples/getopt.h @@ -0,0 +1,180 @@ +/* Declarations for getopt. + Copyright (C) 1989-1994, 1996-1999, 2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library 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. + + The GNU C Library 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 the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _GETOPT_H + +#ifndef __need_getopt +# define _GETOPT_H 1 +#endif + +/* If __GNU_LIBRARY__ is not already defined, either we are being used + standalone, or this is the first header included in the source file. + If we are being used with glibc, we need to include <features.h>, but + that does not exist if we are standalone. So: if __GNU_LIBRARY__ is + not defined, include <ctype.h>, which will pull in <features.h> for us + if it's from glibc. (Why ctype.h? It's guaranteed to exist and it + doesn't flood the namespace with stuff the way some other headers do.) */ +#if !defined __GNU_LIBRARY__ +# include <ctype.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +#ifndef __need_getopt +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +# if (defined __STDC__ && __STDC__) || defined __cplusplus + const char *name; +# else + char *name; +# endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +# define no_argument 0 +# define required_argument 1 +# define optional_argument 2 +#endif /* need getopt */ + + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `getopt' that there are no more + options. + + If OPTS begins with `--', then non-option arguments are treated as + arguments to the option '\0'. This behavior is specific to the GNU + `getopt'. */ + +#if (defined __STDC__ && __STDC__) || defined __cplusplus +# ifdef __GNU_LIBRARY__ +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int __argc, char *const *__argv, const char *__shortopts); +# else /* not __GNU_LIBRARY__ */ +extern int getopt (); +# endif /* __GNU_LIBRARY__ */ + +# ifndef __need_getopt +extern int getopt_long (int __argc, char *const *__argv, const char *__shortopts, + const struct option *__longopts, int *__longind); +extern int getopt_long_only (int __argc, char *const *__argv, + const char *__shortopts, + const struct option *__longopts, int *__longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int __argc, char *const *__argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + int __long_only); +# endif +#else /* not __STDC__ */ +extern int getopt (); +# ifndef __need_getopt +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +# endif +#endif /* __STDC__ */ + +#ifdef __cplusplus +} +#endif + +/* Make sure we later can get all the definitions and declarations. */ +#undef __need_getopt + +#endif /* getopt.h */ diff --git a/examples/getopt1.c b/examples/getopt1.c new file mode 100644 index 0000000..22a7efb --- /dev/null +++ b/examples/getopt1.c @@ -0,0 +1,188 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library 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. + + The GNU C Library 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 the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "getopt.h" + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include <stdio.h> + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +#include <gnu-versions.h> +#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +#define ELIDE_CODE +#endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include <stdlib.h> +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +#include <stdio.h> + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == -1) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/examples/player_example.c b/examples/player_example.c new file mode 100644 index 0000000..dcc02bc --- /dev/null +++ b/examples/player_example.c @@ -0,0 +1,857 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009 * + * by the Xiph.Org Foundation and contributors http://www.xiph.org/ * + * * + ******************************************************************** + + function: example SDL player application; plays Ogg Theora files (with + optional Vorbis audio second stream) + last mod: $Id: player_example.c 16551 2009-09-09 17:53:13Z gmaxwell $ + + ********************************************************************/ + +/* far more complex than most Ogg 'example' programs. The complexity + of maintaining A/V sync is pretty much unavoidable. It's necessary + to actually have audio/video playback to make the hard audio clock + sync actually work. If there's audio playback, there might as well + be simple video playback as well... + + A simple 'demux and write back streams' would have been easier, + it's true. */ + +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif +#if !defined(_LARGEFILE_SOURCE) +#define _LARGEFILE_SOURCE +#endif +#if !defined(_LARGEFILE64_SOURCE) +#define _LARGEFILE64_SOURCE +#endif +#if !defined(_FILE_OFFSET_BITS) +#define _FILE_OFFSET_BITS 64 +#endif + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifndef _REENTRANT +# define _REENTRANT +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <math.h> +#include <signal.h> +#include "theora/theoradec.h" +#include "vorbis/codec.h" +#include <SDL.h> + +/* yes, this makes us OSS-specific for now. None of SDL, libao, libao2 + give us any way to determine hardware timing, and since the + hard/kernel buffer is going to be most of or > a second, that's + just a little bit important */ +#if defined(__FreeBSD__) +#include <machine/soundcard.h> +#define AUDIO_DEVICE "/dev/audio" +#elif defined(__NetBSD__) || defined(__OpenBSD__) +#include <soundcard.h> +#define AUDIO_DEVICE "/dev/audio" +#else +#include <sys/soundcard.h> +#define AUDIO_DEVICE "/dev/dsp" +#endif +#include <sys/ioctl.h> + +/* Helper; just grab some more compressed bitstream and sync it for + page extraction */ +int buffer_data(FILE *in,ogg_sync_state *oy){ + char *buffer=ogg_sync_buffer(oy,4096); + int bytes=fread(buffer,1,4096,in); + ogg_sync_wrote(oy,bytes); + return(bytes); +} + +/* never forget that globals are a one-way ticket to Hell */ +/* Ogg and codec state for demux/decode */ +ogg_sync_state oy; +ogg_page og; +ogg_stream_state vo; +ogg_stream_state to; +th_info ti; +th_comment tc; +th_dec_ctx *td; +th_setup_info *ts; +vorbis_info vi; +vorbis_dsp_state vd; +vorbis_block vb; +vorbis_comment vc; +th_pixel_fmt px_fmt; + +int theora_p=0; +int vorbis_p=0; +int stateflag=0; + +/* SDL Video playback structures */ +SDL_Surface *screen; +SDL_Overlay *yuv_overlay; +SDL_Rect rect; + +/* single frame video buffering */ +int videobuf_ready=0; +ogg_int64_t videobuf_granulepos=-1; +double videobuf_time=0; + +/* single audio fragment audio buffering */ +int audiobuf_fill=0; +int audiobuf_ready=0; +ogg_int16_t *audiobuf; +ogg_int64_t audiobuf_granulepos=0; /* time position of last sample */ + +/* audio / video synchronization tracking: + +Since this will make it to Google at some point and lots of people +search for how to do this, a quick rundown of a practical A/V sync +strategy under Linux [the UNIX where Everything Is Hard]. Naturally, +this works on other platforms using OSS for sound as well. + +In OSS, we don't have reliable access to any precise information on +the exact current playback position (that, of course would have been +too easy; the kernel folks like to keep us app people working hard +doing simple things that should have been solved once and abstracted +long ago). Hopefully ALSA solves this a little better; we'll probably +use that once ALSA is the standard in the stable kernel. + +We can't use the system clock for a/v sync because audio is hard +synced to its own clock, and both the system and audio clocks suffer +from wobble, drift, and a lack of accuracy that can be guaranteed to +add a reliable percent or so of error. After ten seconds, that's +100ms. We can't drift by half a second every minute. + +Although OSS can't generally tell us where the audio playback pointer +is, we do know that if we work in complete audio fragments and keep +the kernel buffer full, a blocking select on the audio buffer will +give us a writable fragment immediately after playback finishes with +it. We assume at that point that we know the exact number of bytes in +the kernel buffer that have not been played (total fragments minus +one) and calculate clock drift between audio and system then (and only +then). Damp the sync correction fraction, apply, and walla: A +reliable A/V clock that even works if it's interrupted. */ + +long audiofd_totalsize=-1; +int audiofd_fragsize; /* read and write only complete fragments + so that SNDCTL_DSP_GETOSPACE is + accurate immediately after a bank + switch */ +int audiofd=-1; +ogg_int64_t audiofd_timer_calibrate=-1; + +static void open_audio(){ + audio_buf_info info; + int format=AFMT_S16_NE; /* host endian */ + int channels=vi.channels; + int rate=vi.rate; + int ret; + + audiofd=open(AUDIO_DEVICE,O_RDWR); + if(audiofd<0){ + fprintf(stderr,"Could not open audio device " AUDIO_DEVICE ".\n"); + exit(1); + } + + ret=ioctl(audiofd,SNDCTL_DSP_SETFMT,&format); + if(ret){ + fprintf(stderr,"Could not set 16 bit host-endian playback\n"); + exit(1); + } + + ret=ioctl(audiofd,SNDCTL_DSP_CHANNELS,&channels); + if(ret){ + fprintf(stderr,"Could not set %d channel playback\n",channels); + exit(1); + } + + ret=ioctl(audiofd,SNDCTL_DSP_SPEED,&rate); + if(ret){ + fprintf(stderr,"Could not set %d Hz playback\n",rate); + exit(1); + } + + ioctl(audiofd,SNDCTL_DSP_GETOSPACE,&info); + audiofd_fragsize=info.fragsize; + audiofd_totalsize=info.fragstotal*info.fragsize; + + audiobuf=malloc(audiofd_fragsize); +} + +static void audio_close(void){ + if(audiofd>-1){ + ioctl(audiofd,SNDCTL_DSP_RESET,NULL); + close(audiofd); + free(audiobuf); + } +} + +/* call this only immediately after unblocking from a full kernel + having a newly empty fragment or at the point of DMA restart */ +void audio_calibrate_timer(int restart){ + struct timeval tv; + ogg_int64_t current_sample; + ogg_int64_t new_time; + + gettimeofday(&tv,0); + new_time=tv.tv_sec*1000+tv.tv_usec/1000; + + if(restart){ + current_sample=audiobuf_granulepos-audiobuf_fill/2/vi.channels; + }else + current_sample=audiobuf_granulepos- + (audiobuf_fill+audiofd_totalsize-audiofd_fragsize)/2/vi.channels; + + new_time-=1000*current_sample/vi.rate; + + audiofd_timer_calibrate=new_time; +} + +/* get relative time since beginning playback, compensating for A/V + drift */ +double get_time(){ + static ogg_int64_t last=0; + static ogg_int64_t up=0; + ogg_int64_t now; + struct timeval tv; + + gettimeofday(&tv,0); + now=tv.tv_sec*1000+tv.tv_usec/1000; + + if(audiofd_timer_calibrate==-1)audiofd_timer_calibrate=last=now; + + if(audiofd<0){ + /* no audio timer to worry about, we can just use the system clock */ + /* only one complication: If the process is suspended, we should + reset timing to account for the gap in play time. Do it the + easy/hack way */ + if(now-last>1000)audiofd_timer_calibrate+=(now-last); + last=now; + } + + if(now-up>200){ + double timebase=(now-audiofd_timer_calibrate)*.001; + int hundredths=timebase*100-(long)timebase*100; + int seconds=(long)timebase%60; + int minutes=((long)timebase/60)%60; + int hours=(long)timebase/3600; + + fprintf(stderr," Playing: %d:%02d:%02d.%02d \r", + hours,minutes,seconds,hundredths); + up=now; + } + + return (now-audiofd_timer_calibrate)*.001; + +} + +/* write a fragment to the OSS kernel audio API, but only if we can + stuff in a whole fragment without blocking */ +void audio_write_nonblocking(void){ + + if(audiobuf_ready){ + audio_buf_info info; + long bytes; + + ioctl(audiofd,SNDCTL_DSP_GETOSPACE,&info); + bytes=info.bytes; + if(bytes>=audiofd_fragsize){ + if(bytes==audiofd_totalsize)audio_calibrate_timer(1); + + while(1){ + bytes=write(audiofd,audiobuf+(audiofd_fragsize-audiobuf_fill), + audiofd_fragsize); + + if(bytes>0){ + + if(bytes!=audiobuf_fill){ + /* shouldn't actually be possible... but eh */ + audiobuf_fill-=bytes; + }else + break; + } + } + + audiobuf_fill=0; + audiobuf_ready=0; + + } + } +} + +/* clean quit on Ctrl-C for SDL and thread shutdown as per SDL example + (we don't use any threads, but libSDL does) */ +int got_sigint=0; +static void sigint_handler (int signal) { + got_sigint = 1; +} + +static void open_video(void){ + int w; + int h; + w=(ti.pic_x+ti.frame_width+1&~1)-(ti.pic_x&~1); + h=(ti.pic_y+ti.frame_height+1&~1)-(ti.pic_y&~1); + if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) { + fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError()); + exit(1); + } + + screen = SDL_SetVideoMode(w, h, 0, SDL_SWSURFACE); + if ( screen == NULL ) { + fprintf(stderr, "Unable to set %dx%d video: %s\n", + w,h,SDL_GetError()); + exit(1); + } + + if (px_fmt==TH_PF_422) + yuv_overlay = SDL_CreateYUVOverlay(w, h, + SDL_YUY2_OVERLAY, + screen); + else + yuv_overlay = SDL_CreateYUVOverlay(w, h, + SDL_YV12_OVERLAY, + screen); + + if ( yuv_overlay == NULL ) { + fprintf(stderr, "SDL: Couldn't create SDL_yuv_overlay: %s\n", + SDL_GetError()); + exit(1); + } + rect.x = 0; + rect.y = 0; + rect.w = w; + rect.h = h; + + SDL_DisplayYUVOverlay(yuv_overlay, &rect); +} + +static void video_write(void){ + int i; + th_ycbcr_buffer yuv; + int y_offset, uv_offset; + th_decode_ycbcr_out(td,yuv); + /* Lock SDL_yuv_overlay */ + if ( SDL_MUSTLOCK(screen) ) { + if ( SDL_LockSurface(screen) < 0 ) return; + } + if (SDL_LockYUVOverlay(yuv_overlay) < 0) return; + + /* let's draw the data on a SDL screen (*screen) */ + /* deal with border stride */ + /* reverse u and v for SDL */ + /* and crop input properly, respecting the encoded frame rect */ + /* problems may exist for odd frame rect for some encodings */ + + y_offset=(ti.pic_x&~1)+yuv[0].stride*(ti.pic_y&~1); + + if (px_fmt==TH_PF_422) { + uv_offset=(ti.pic_x/2)+(yuv[1].stride)*(ti.pic_y); + /* SDL doesn't have a planar 4:2:2 */ + for(i=0;i<yuv_overlay->h;i++) { + int j; + char *in_y = (char *)yuv[0].data+y_offset+yuv[0].stride*i; + char *out = (char *)(yuv_overlay->pixels[0]+yuv_overlay->pitches[0]*i); + for (j=0;j<yuv_overlay->w;j++) + out[j*2] = in_y[j]; + char *in_u = (char *)yuv[1].data+uv_offset+yuv[1].stride*i; + char *in_v = (char *)yuv[2].data+uv_offset+yuv[2].stride*i; + for (j=0;j<yuv_overlay->w>>1;j++) { + out[j*4+1] = in_u[j]; + out[j*4+3] = in_v[j]; + } + } + } else { + uv_offset=(ti.pic_x/2)+(yuv[1].stride)*(ti.pic_y/2); + for(i=0;i<yuv_overlay->h;i++) + memcpy(yuv_overlay->pixels[0]+yuv_overlay->pitches[0]*i, + yuv[0].data+y_offset+yuv[0].stride*i, + yuv_overlay->w); + for(i=0;i<yuv_overlay->h/2;i++){ + memcpy(yuv_overlay->pixels[1]+yuv_overlay->pitches[1]*i, + yuv[2].data+uv_offset+yuv[2].stride*i, + yuv_overlay->w/2); + memcpy(yuv_overlay->pixels[2]+yuv_overlay->pitches[2]*i, + yuv[1].data+uv_offset+yuv[1].stride*i, + yuv_overlay->w/2); + } + } + + /* Unlock SDL_yuv_overlay */ + if ( SDL_MUSTLOCK(screen) ) { + SDL_UnlockSurface(screen); + } + SDL_UnlockYUVOverlay(yuv_overlay); + + + /* Show, baby, show! */ + SDL_DisplayYUVOverlay(yuv_overlay, &rect); + +} +/* dump the theora (or vorbis) comment header */ +static int dump_comments(th_comment *tc){ + int i, len; + char *value; + FILE *out=stdout; + + fprintf(out,"Encoded by %s\n",tc->vendor); + if(tc->comments){ + fprintf(out, "theora comment header:\n"); + for(i=0;i<tc->comments;i++){ + if(tc->user_comments[i]){ + len=tc->comment_lengths[i]; + value=malloc(len+1); + memcpy(value,tc->user_comments[i],len); + value[len]='\0'; + fprintf(out, "\t%s\n", value); + free(value); + } + } + } + return(0); +} + +/* Report the encoder-specified colorspace for the video, if any. + We don't actually make use of the information in this example; + a real player should attempt to perform color correction for + whatever display device it supports. */ +static void report_colorspace(th_info *ti) +{ + switch(ti->colorspace){ + case TH_CS_UNSPECIFIED: + /* nothing to report */ + break;; + case TH_CS_ITU_REC_470M: + fprintf(stderr," encoder specified ITU Rec 470M (NTSC) color.\n"); + break;; + case TH_CS_ITU_REC_470BG: + fprintf(stderr," encoder specified ITU Rec 470BG (PAL) color.\n"); + break;; + default: + fprintf(stderr,"warning: encoder specified unknown colorspace (%d).\n", + ti->colorspace); + break;; + } +} + +/* helper: push a page into the appropriate steam */ +/* this can be done blindly; a stream won't accept a page + that doesn't belong to it */ +static int queue_page(ogg_page *page){ + if(theora_p)ogg_stream_pagein(&to,page); + if(vorbis_p)ogg_stream_pagein(&vo,page); + return 0; +} + +static void usage(void){ + fprintf(stderr, + "Usage: player_example <file.ogv>\n" + "input is read from stdin if no file is passed on the command line\n" + "\n" + ); +} + +int main(int argc,char *const *argv){ + + int pp_level_max; + int pp_level; + int pp_inc; + int i,j; + ogg_packet op; + + FILE *infile = stdin; + + int frames = 0; + int dropped = 0; + +#ifdef _WIN32 /* We need to set stdin/stdout to binary mode. Damn windows. */ + /* Beware the evil ifdef. We avoid these where we can, but this one we + cannot. Don't add any more, you'll probably go to hell if you do. */ + _setmode( _fileno( stdin ), _O_BINARY ); +#endif + + /* open the input file if any */ + if(argc==2){ + infile=fopen(argv[1],"rb"); + if(infile==NULL){ + fprintf(stderr,"Unable to open '%s' for playback.\n", argv[1]); + exit(1); + } + } + if(argc>2){ + usage(); + exit(1); + } + + /* start up Ogg stream synchronization layer */ + ogg_sync_init(&oy); + + /* init supporting Vorbis structures needed in header parsing */ + vorbis_info_init(&vi); + vorbis_comment_init(&vc); + + /* init supporting Theora structures needed in header parsing */ + th_comment_init(&tc); + th_info_init(&ti); + + /* Ogg file open; parse the headers */ + /* Only interested in Vorbis/Theora streams */ + while(!stateflag){ + int ret=buffer_data(infile,&oy); + if(ret==0)break; + while(ogg_sync_pageout(&oy,&og)>0){ + ogg_stream_state test; + + /* is this a mandated initial header? If not, stop parsing */ + if(!ogg_page_bos(&og)){ + /* don't leak the page; get it into the appropriate stream */ + queue_page(&og); + stateflag=1; + break; + } + + ogg_stream_init(&test,ogg_page_serialno(&og)); + ogg_stream_pagein(&test,&og); + ogg_stream_packetout(&test,&op); + + + /* identify the codec: try theora */ + if(!theora_p && th_decode_headerin(&ti,&tc,&ts,&op)>=0){ + /* it is theora */ + memcpy(&to,&test,sizeof(test)); + theora_p=1; + }else if(!vorbis_p && vorbis_synthesis_headerin(&vi,&vc,&op)>=0){ + /* it is vorbis */ + memcpy(&vo,&test,sizeof(test)); + vorbis_p=1; + }else{ + /* whatever it is, we don't care about it */ + ogg_stream_clear(&test); + } + } + /* fall through to non-bos page parsing */ + } + + /* we're expecting more header packets. */ + while((theora_p && theora_p<3) || (vorbis_p && vorbis_p<3)){ + int ret; + + /* look for further theora headers */ + while(theora_p && (theora_p<3) && (ret=ogg_stream_packetout(&to,&op))){ + if(ret<0){ + fprintf(stderr,"Error parsing Theora stream headers; " + "corrupt stream?\n"); + exit(1); + } + if(!th_decode_headerin(&ti,&tc,&ts,&op)){ + fprintf(stderr,"Error parsing Theora stream headers; " + "corrupt stream?\n"); + exit(1); + } + theora_p++; + } + + /* look for more vorbis header packets */ + while(vorbis_p && (vorbis_p<3) && (ret=ogg_stream_packetout(&vo,&op))){ + if(ret<0){ + fprintf(stderr,"Error parsing Vorbis stream headers; corrupt stream?\n"); + exit(1); + } + if(vorbis_synthesis_headerin(&vi,&vc,&op)){ + fprintf(stderr,"Error parsing Vorbis stream headers; corrupt stream?\n"); + exit(1); + } + vorbis_p++; + if(vorbis_p==3)break; + } + + /* The header pages/packets will arrive before anything else we + care about, or the stream is not obeying spec */ + + if(ogg_sync_pageout(&oy,&og)>0){ + queue_page(&og); /* demux into the appropriate stream */ + }else{ + int ret=buffer_data(infile,&oy); /* someone needs more data */ + if(ret==0){ + fprintf(stderr,"End of file while searching for codec headers.\n"); + exit(1); + } + } + } + + /* and now we have it all. initialize decoders */ + if(theora_p){ + td=th_decode_alloc(&ti,ts); + printf("Ogg logical stream %lx is Theora %dx%d %.02f fps", + to.serialno,ti.pic_width,ti.pic_height, + (double)ti.fps_numerator/ti.fps_denominator); + px_fmt=ti.pixel_fmt; + switch(ti.pixel_fmt){ + case TH_PF_420: printf(" 4:2:0 video\n"); break; + case TH_PF_422: printf(" 4:2:2 video\n"); break; + case TH_PF_444: printf(" 4:4:4 video\n"); break; + case TH_PF_RSVD: + default: + printf(" video\n (UNKNOWN Chroma sampling!)\n"); + break; + } + if(ti.pic_width!=ti.frame_width || ti.pic_height!=ti.frame_height) + printf(" Frame content is %dx%d with offset (%d,%d).\n", + ti.frame_width, ti.frame_height, ti.pic_x, ti.pic_y); + report_colorspace(&ti); + dump_comments(&tc); + th_decode_ctl(td,TH_DECCTL_GET_PPLEVEL_MAX,&pp_level_max, + sizeof(pp_level_max)); + pp_level=pp_level_max; + th_decode_ctl(td,TH_DECCTL_SET_PPLEVEL,&pp_level,sizeof(pp_level)); + pp_inc=0; + + /*{ + int arg = 0xffff; + th_decode_ctl(td,TH_DECCTL_SET_TELEMETRY_MBMODE,&arg,sizeof(arg)); + th_decode_ctl(td,TH_DECCTL_SET_TELEMETRY_MV,&arg,sizeof(arg)); + th_decode_ctl(td,TH_DECCTL_SET_TELEMETRY_QI,&arg,sizeof(arg)); + arg=10; + th_decode_ctl(td,TH_DECCTL_SET_TELEMETRY_BITS,&arg,sizeof(arg)); + }*/ + }else{ + /* tear down the partial theora setup */ + th_info_clear(&ti); + th_comment_clear(&tc); + } + + th_setup_free(ts); + + if(vorbis_p){ + vorbis_synthesis_init(&vd,&vi); + vorbis_block_init(&vd,&vb); + fprintf(stderr,"Ogg logical stream %lx is Vorbis %d channel %ld Hz audio.\n", + vo.serialno,vi.channels,vi.rate); + }else{ + /* tear down the partial vorbis setup */ + vorbis_info_clear(&vi); + vorbis_comment_clear(&vc); + } + + /* open audio */ + if(vorbis_p)open_audio(); + + /* open video */ + if(theora_p)open_video(); + + /* install signal handler as SDL clobbered the default */ + signal (SIGINT, sigint_handler); + + /* on to the main decode loop. We assume in this example that audio + and video start roughly together, and don't begin playback until + we have a start frame for both. This is not necessarily a valid + assumption in Ogg A/V streams! It will always be true of the + example_encoder (and most streams) though. */ + + stateflag=0; /* playback has not begun */ + while(!got_sigint){ + + /* we want a video and audio frame ready to go at all times. If + we have to buffer incoming, buffer the compressed data (ie, let + ogg do the buffering) */ + while(vorbis_p && !audiobuf_ready){ + int ret; + float **pcm; + + /* if there's pending, decoded audio, grab it */ + if((ret=vorbis_synthesis_pcmout(&vd,&pcm))>0){ + int count=audiobuf_fill/2; + int maxsamples=(audiofd_fragsize-audiobuf_fill)/2/vi.channels; + for(i=0;i<ret && i<maxsamples;i++) + for(j=0;j<vi.channels;j++){ + int val=rint(pcm[j][i]*32767.f); + if(val>32767)val=32767; + if(val<-32768)val=-32768; + audiobuf[count++]=val; + } + vorbis_synthesis_read(&vd,i); + audiobuf_fill+=i*vi.channels*2; + if(audiobuf_fill==audiofd_fragsize)audiobuf_ready=1; + if(vd.granulepos>=0) + audiobuf_granulepos=vd.granulepos-ret+i; + else + audiobuf_granulepos+=i; + + }else{ + + /* no pending audio; is there a pending packet to decode? */ + if(ogg_stream_packetout(&vo,&op)>0){ + if(vorbis_synthesis(&vb,&op)==0) /* test for success! */ + vorbis_synthesis_blockin(&vd,&vb); + }else /* we need more data; break out to suck in another page */ + break; + } + } + + while(theora_p && !videobuf_ready){ + /* theora is one in, one out... */ + if(ogg_stream_packetout(&to,&op)>0){ + + if(pp_inc){ + pp_level+=pp_inc; + th_decode_ctl(td,TH_DECCTL_SET_PPLEVEL,&pp_level, + sizeof(pp_level)); + pp_inc=0; + } + /*HACK: This should be set after a seek or a gap, but we might not have + a granulepos for the first packet (we only have them for the last + packet on a page), so we just set it as often as we get it. + To do this right, we should back-track from the last packet on the + page and compute the correct granulepos for the first packet after + a seek or a gap.*/ + if(op.granulepos>=0){ + th_decode_ctl(td,TH_DECCTL_SET_GRANPOS,&op.granulepos, + sizeof(op.granulepos)); + } + if(th_decode_packetin(td,&op,&videobuf_granulepos)==0){ + videobuf_time=th_granule_time(td,videobuf_granulepos); + frames++; + + /* is it already too old to be useful? This is only actually + useful cosmetically after a SIGSTOP. Note that we have to + decode the frame even if we don't show it (for now) due to + keyframing. Soon enough libtheora will be able to deal + with non-keyframe seeks. */ + + if(videobuf_time>=get_time()) + videobuf_ready=1; + else{ + /*If we are too slow, reduce the pp level.*/ + pp_inc=pp_level>0?-1:0; + dropped++; + } + } + + }else + break; + } + + if(!videobuf_ready && !audiobuf_ready && feof(infile))break; + + if(!videobuf_ready || !audiobuf_ready){ + /* no data yet for somebody. Grab another page */ + buffer_data(infile,&oy); + while(ogg_sync_pageout(&oy,&og)>0){ + queue_page(&og); + } + } + + /* If playback has begun, top audio buffer off immediately. */ + if(stateflag) audio_write_nonblocking(); + + /* are we at or past time for this video frame? */ + if(stateflag && videobuf_ready && videobuf_time<=get_time()){ + video_write(); + videobuf_ready=0; + } + + if(stateflag && + (audiobuf_ready || !vorbis_p) && + (videobuf_ready || !theora_p) && + !got_sigint){ + /* we have an audio frame ready (which means the audio buffer is + full), it's not time to play video, so wait until one of the + audio buffer is ready or it's near time to play video */ + + /* set up select wait on the audiobuffer and a timeout for video */ + struct timeval timeout; + fd_set writefs; + fd_set empty; + int n=0; + + FD_ZERO(&writefs); + FD_ZERO(&empty); + if(audiofd>=0){ + FD_SET(audiofd,&writefs); + n=audiofd+1; + } + + if(theora_p){ + double tdiff; + long milliseconds; + tdiff=videobuf_time-get_time(); + /*If we have lots of extra time, increase the post-processing level.*/ + if(tdiff>ti.fps_denominator*0.25/ti.fps_numerator){ + pp_inc=pp_level<pp_level_max?1:0; + } + else if(tdiff<ti.fps_denominator*0.05/ti.fps_numerator){ + pp_inc=pp_level>0?-1:0; + } + milliseconds=tdiff*1000-5; + if(milliseconds>500)milliseconds=500; + if(milliseconds>0){ + timeout.tv_sec=milliseconds/1000; + timeout.tv_usec=(milliseconds%1000)*1000; + + n=select(n,&empty,&writefs,&empty,&timeout); + if(n)audio_calibrate_timer(0); + } + }else{ + select(n,&empty,&writefs,&empty,NULL); + } + } + + /* if our buffers either don't exist or are ready to go, + we can begin playback */ + if((!theora_p || videobuf_ready) && + (!vorbis_p || audiobuf_ready))stateflag=1; + /* same if we've run out of input */ + if(feof(infile))stateflag=1; + + } + + /* tear it all down */ + + audio_close(); + SDL_Quit(); + + if(vorbis_p){ + ogg_stream_clear(&vo); + vorbis_block_clear(&vb); + vorbis_dsp_clear(&vd); + vorbis_comment_clear(&vc); + vorbis_info_clear(&vi); + } + if(theora_p){ + ogg_stream_clear(&to); + th_decode_free(td); + th_comment_clear(&tc); + th_info_clear(&ti); + } + ogg_sync_clear(&oy); + + if(infile && infile!=stdin)fclose(infile); + + fprintf(stderr, + "\r \r"); + fprintf(stderr, "%d frames", frames); + if (dropped) fprintf(stderr, " (%d dropped)", dropped); + fprintf(stderr, "\n"); + fprintf(stderr, "\nDone.\n"); + + return(0); + +} diff --git a/examples/png2theora.c b/examples/png2theora.c new file mode 100644 index 0000000..71a0d0f --- /dev/null +++ b/examples/png2theora.c @@ -0,0 +1,912 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009,2009 * + * by the Xiph.Org Foundation and contributors http://www.xiph.org/ * + * * + ******************************************************************** + + function: example encoder application; makes an Ogg Theora + file from a sequence of png images + last mod: $Id: png2theora.c 16503 2009-08-22 18:14:02Z giles $ + based on code from Vegard Nossum + + ********************************************************************/ + +#define _FILE_OFFSET_BITS 64 + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> +#include <math.h> +#include <libgen.h> +#include <sys/types.h> +#include <dirent.h> + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <png.h> +#include <ogg/ogg.h> +#include "theora/theoraenc.h" + +#define PROGRAM_NAME "png2theora" +#define PROGRAM_VERSION "1.1" + +static const char *option_output = NULL; +static int video_fps_numerator = 24; +static int video_fps_denominator = 1; +static int video_aspect_numerator = 0; +static int video_aspect_denominator = 0; +static int video_rate = -1; +static int video_quality = -1; +ogg_uint32_t keyframe_frequency=0; +int buf_delay=-1; +int vp3_compatible=0; +static int chroma_format = TH_PF_420; + +static FILE *twopass_file = NULL; +static int twopass=0; +static int passno; + +static FILE *ogg_fp = NULL; +static ogg_stream_state ogg_os; +static ogg_packet op; +static ogg_page og; + +static th_enc_ctx *td; +static th_info ti; + +static char *input_filter; + +const char *optstring = "o:hv:\4:\2:V:s:S:f:F:ck:d:\1\2\3\4\5\6"; +struct option options [] = { + {"output",required_argument,NULL,'o'}, + {"help",no_argument,NULL,'h'}, + {"chroma-444",no_argument,NULL,'\5'}, + {"chroma-422",no_argument,NULL,'\6'}, + {"video-rate-target",required_argument,NULL,'V'}, + {"video-quality",required_argument,NULL,'v'}, + {"aspect-numerator",required_argument,NULL,'s'}, + {"aspect-denominator",required_argument,NULL,'S'}, + {"framerate-numerator",required_argument,NULL,'f'}, + {"framerate-denominator",required_argument,NULL,'F'}, + {"vp3-compatible",no_argument,NULL,'c'}, + {"soft-target",no_argument,NULL,'\1'}, + {"keyframe-freq",required_argument,NULL,'k'}, + {"buf-delay",required_argument,NULL,'d'}, + {"two-pass",no_argument,NULL,'\2'}, + {"first-pass",required_argument,NULL,'\3'}, + {"second-pass",required_argument,NULL,'\4'}, + {NULL,0,NULL,0} +}; + +static void usage(void){ + fprintf(stderr, + "%s %s\n" + "Usage: %s [options] <input>\n\n" + "The input argument uses C printf format to represent a list of files,\n" + " i.e. file-%%06d.png to look for files file000001.png to file9999999.png \n\n" + "Options: \n\n" + " -o --output <filename.ogv> file name for encoded output (required);\n" + " -v --video-quality <n> Theora quality selector fro 0 to 10\n" + " (0 yields smallest files but lowest\n" + " video quality. 10 yields highest\n" + " fidelity but large files)\n\n" + " -V --video-rate-target <n> bitrate target for Theora video\n\n" + " --soft-target Use a large reservoir and treat the rate\n" + " as a soft target; rate control is less\n" + " strict but resulting quality is usually\n" + " higher/smoother overall. Soft target also\n" + " allows an optional -v setting to specify\n" + " a minimum allowed quality.\n\n" + " --two-pass Compress input using two-pass rate control\n" + " This option performs both passes automatically.\n\n" + " --first-pass <filename> Perform first-pass of a two-pass rate\n" + " controlled encoding, saving pass data to\n" + " <filename> for a later second pass\n\n" + " --second-pass <filename> Perform second-pass of a two-pass rate\n" + " controlled encoding, reading first-pass\n" + " data from <filename>. The first pass\n" + " data must come from a first encoding pass\n" + " using identical input video to work\n" + " properly.\n\n" + " -k --keyframe-freq <n> Keyframe frequency\n" + " -d --buf-delay <n> Buffer delay (in frames). Longer delays\n" + " allow smoother rate adaptation and provide\n" + " better overall quality, but require more\n" + " client side buffering and add latency. The\n" + " default value is the keyframe interval for\n" + " one-pass encoding (or somewhat larger if\n" + " --soft-target is used) and infinite for\n" + " two-pass encoding.\n" + " --chroma-444 Use 4:4:4 chroma subsampling\n" + " --chroma-422 Use 4:2:2 chroma subsampling\n" + " (4:2:0 is default)\n\n" + " -s --aspect-numerator <n> Aspect ratio numerator, default is 0\n" + " -S --aspect-denominator <n> Aspect ratio denominator, default is 0\n" + " -f --framerate-numerator <n> Frame rate numerator\n" + " -F --framerate-denominator <n> Frame rate denominator\n" + " The frame rate nominator divided by this\n" + " determines the frame rate in units per tick\n" + ,PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_NAME + ); + exit(0); +} + +#ifdef WIN32 +int +alphasort (const void *a, const void *b) +{ + return strcoll ((*(const struct dirent **) a)->d_name, + (*(const struct dirent **) b)->d_name); +} + +int +scandir (const char *dir, struct dirent ***namelist, + int (*select)(const struct dirent *), int (*compar)(const void *, const void *)) +{ + DIR *d; + struct dirent *entry; + register int i=0; + size_t entrysize; + + if ((d=opendir(dir)) == NULL) + return(-1); + + *namelist=NULL; + while ((entry=readdir(d)) != NULL) + { + if (select == NULL || (select != NULL && (*select)(entry))) + { + *namelist=(struct dirent **)realloc((void *)(*namelist), + (size_t)((i+1)*sizeof(struct dirent *))); + if (*namelist == NULL) return(-1); + entrysize=sizeof(struct dirent)-sizeof(entry->d_name)+strlen(entry->d_name)+1; + (*namelist)[i]=(struct dirent *)malloc(entrysize); + if ((*namelist)[i] == NULL) return(-1); + memcpy((*namelist)[i], entry, entrysize); + i++; + } + } + if (closedir(d)) return(-1); + if (i == 0) return(-1); + if (compar != NULL) + qsort((void *)(*namelist), (size_t)i, sizeof(struct dirent *), compar); + + return(i); +} +#endif + +static int +theora_write_frame(unsigned long w, unsigned long h, unsigned char *yuv, int last) +{ + th_ycbcr_buffer ycbcr; + ogg_packet op; + ogg_page og; + + unsigned long yuv_w; + unsigned long yuv_h; + + unsigned char *yuv_y; + unsigned char *yuv_u; + unsigned char *yuv_v; + + unsigned int x; + unsigned int y; + + /* Must hold: yuv_w >= w */ + yuv_w = (w + 15) & ~15; + + /* Must hold: yuv_h >= h */ + yuv_h = (h + 15) & ~15; + + ycbcr[0].width = yuv_w; + ycbcr[0].height = yuv_h; + ycbcr[0].stride = yuv_w; + ycbcr[1].width = (chroma_format == TH_PF_444) ? yuv_w : (yuv_w >> 1); + ycbcr[1].stride = ycbcr[1].width; + ycbcr[1].height = (chroma_format == TH_PF_420) ? (yuv_h >> 1) : yuv_h; + ycbcr[2].width = ycbcr[1].width; + ycbcr[2].stride = ycbcr[1].stride; + ycbcr[2].height = ycbcr[1].height; + + ycbcr[0].data = yuv_y = malloc(ycbcr[0].stride * ycbcr[0].height); + ycbcr[1].data = yuv_u = malloc(ycbcr[1].stride * ycbcr[1].height); + ycbcr[2].data = yuv_v = malloc(ycbcr[2].stride * ycbcr[2].height); + + for(y = 0; y < h; y++) { + for(x = 0; x < w; x++) { + yuv_y[x + y * yuv_w] = yuv[3 * (x + y * w) + 0]; + } + } + + if (chroma_format == TH_PF_420) { + for(y = 0; y < h; y += 2) { + for(x = 0; x < w; x += 2) { + yuv_u[(x >> 1) + (y >> 1) * (yuv_w >> 1)] = + yuv[3 * (x + y * w) + 1]; + yuv_v[(x >> 1) + (y >> 1) * (yuv_w >> 1)] = + yuv[3 * (x + y * w) + 2]; + } + } + } else if (chroma_format == TH_PF_444) { + for(y = 0; y < h; y++) { + for(x = 0; x < w; x++) { + yuv_u[x + y * ycbcr[1].stride] = yuv[3 * (x + y * w) + 1]; + yuv_v[x + y * ycbcr[2].stride] = yuv[3 * (x + y * w) + 2]; + } + } + } else { /* TH_PF_422 */ + for(y = 0; y < h; y += 1) { + for(x = 0; x < w; x += 2) { + yuv_u[(x >> 1) + y * ycbcr[1].stride] = + yuv[3 * (x + y * w) + 1]; + yuv_v[(x >> 1) + y * ycbcr[2].stride] = + yuv[3 * (x + y * w) + 2]; + } + } + } + + /* Theora is a one-frame-in,one-frame-out system; submit a frame + for compression and pull out the packet */ + /* in two-pass mode's second pass, we need to submit first-pass data */ + if(passno==2){ + int ret; + for(;;){ + static unsigned char buffer[80]; + static int buf_pos; + int bytes; + /*Ask the encoder how many bytes it would like.*/ + bytes=th_encode_ctl(td,TH_ENCCTL_2PASS_IN,NULL,0); + if(bytes<0){ + fprintf(stderr,"Error submitting pass data in second pass.\n"); + exit(1); + } + /*If it's got enough, stop.*/ + if(bytes==0)break; + /*Read in some more bytes, if necessary.*/ + if(bytes>80-buf_pos)bytes=80-buf_pos; + if(bytes>0&&fread(buffer+buf_pos,1,bytes,twopass_file)<bytes){ + fprintf(stderr,"Could not read frame data from two-pass data file!\n"); + exit(1); + } + /*And pass them off.*/ + ret=th_encode_ctl(td,TH_ENCCTL_2PASS_IN,buffer,bytes); + if(ret<0){ + fprintf(stderr,"Error submitting pass data in second pass.\n"); + exit(1); + } + /*If the encoder consumed the whole buffer, reset it.*/ + if(ret>=bytes)buf_pos=0; + /*Otherwise remember how much it used.*/ + else buf_pos+=ret; + } + } + + if(th_encode_ycbcr_in(td, ycbcr)) { + fprintf(stderr, "%s: error: could not encode frame\n", + option_output); + return 1; + } + + /* in two-pass mode's first pass we need to extract and save the pass data */ + if(passno==1){ + unsigned char *buffer; + int bytes = th_encode_ctl(td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer)); + if(bytes<0){ + fprintf(stderr,"Could not read two-pass data from encoder.\n"); + exit(1); + } + if(fwrite(buffer,1,bytes,twopass_file)<bytes){ + fprintf(stderr,"Unable to write to two-pass data file.\n"); + exit(1); + } + fflush(twopass_file); + } + + if(!th_encode_packetout(td, last, &op)) { + fprintf(stderr, "%s: error: could not read packets\n", + option_output); + return 1; + } + + if (passno!=1) { + ogg_stream_packetin(&ogg_os, &op); + while(ogg_stream_pageout(&ogg_os, &og)) { + fwrite(og.header, og.header_len, 1, ogg_fp); + fwrite(og.body, og.body_len, 1, ogg_fp); + } + } + + free(yuv_y); + free(yuv_u); + free(yuv_v); + + return 0; +} + +static unsigned char +clamp(double d) +{ + if(d < 0) + return 0; + + if(d > 255) + return 255; + + return d; +} + +static void +rgb_to_yuv(png_bytep *png, + unsigned char *yuv, + unsigned int w, unsigned int h) +{ + unsigned int x; + unsigned int y; + + for(y = 0; y < h; y++) { + for(x = 0; x < w; x++) { + png_byte r; + png_byte g; + png_byte b; + + r = png[y][3 * x + 0]; + g = png[y][3 * x + 1]; + b = png[y][3 * x + 2]; + + /* XXX: Cringe. */ + yuv[3 * (x + w * y) + 0] = clamp( + 0.299 * r + + 0.587 * g + + 0.114 * b); + yuv[3 * (x + w * y) + 1] = clamp((0.436 * 255 + - 0.14713 * r + - 0.28886 * g + + 0.436 * b) / 0.872); + yuv[3 * (x + w * y) + 2] = clamp((0.615 * 255 + + 0.615 * r + - 0.51499 * g + - 0.10001 * b) / 1.230); + } + } +} + +static int +png_read(const char *pathname, unsigned int *w, unsigned int *h, unsigned char **yuv) +{ + FILE *fp; + unsigned char header[8]; + png_structp png_ptr; + png_infop info_ptr; + png_infop end_ptr; + png_bytep row_data; + png_bytep *row_pointers; + png_color_16p bkgd; + png_uint_32 width; + png_uint_32 height; + int bit_depth; + int color_type; + int interlace_type; + int compression_type; + int filter_method; + png_uint_32 y; + + fp = fopen(pathname, "rb"); + if(!fp) { + fprintf(stderr, "%s: error: %s\n", + pathname, strerror(errno)); + return 1; + } + + fread(header, 1, 8, fp); + if(png_sig_cmp(header, 0, 8)) { + fprintf(stderr, "%s: error: %s\n", + pathname, "not a PNG"); + fclose(fp); + return 1; + } + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if(!png_ptr) { + fprintf(stderr, "%s: error: %s\n", + pathname, "couldn't create png read structure"); + fclose(fp); + return 1; + } + + info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) { + fprintf(stderr, "%s: error: %s\n", + pathname, "couldn't create png info structure"); + png_destroy_read_struct(&png_ptr, NULL, NULL); + fclose(fp); + return 1; + } + + end_ptr = png_create_info_struct(png_ptr); + if(!end_ptr) { + fprintf(stderr, "%s: error: %s\n", + pathname, "couldn't create png info structure"); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return 1; + } + + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, 8); + png_read_info(png_ptr, info_ptr); + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + &interlace_type, &compression_type, &filter_method); + png_set_expand(png_ptr); + if(bit_depth<8)png_set_packing(png_ptr); + if(bit_depth==16)png_set_strip_16(png_ptr); + if(!(color_type&PNG_COLOR_MASK_COLOR))png_set_gray_to_rgb(png_ptr); + if(png_get_bKGD(png_ptr, info_ptr, &bkgd)){ + png_set_background(png_ptr, bkgd, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); + } + /*Note that color_type 2 and 3 can also have alpha, despite not setting the + PNG_COLOR_MASK_ALPHA bit. + We always strip it to prevent libpng from overrunning our buffer.*/ + png_set_strip_alpha(png_ptr); + + row_data = (png_bytep)png_malloc(png_ptr, + 3*height*width*png_sizeof(*row_data)); + row_pointers = (png_bytep *)png_malloc(png_ptr, + height*png_sizeof(*row_pointers)); + for(y = 0; y < height; y++) { + row_pointers[y] = row_data + y*(3*width); + } + png_read_image(png_ptr, row_pointers); + png_read_end(png_ptr, end_ptr); + + *w = width; + *h = height; + *yuv = malloc(*w * *h * 3); + rgb_to_yuv(row_pointers, *yuv, *w, *h); + + png_free(png_ptr, row_pointers); + png_free(png_ptr, row_data); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_ptr); + + fclose(fp); + return 0; +} + +static int include_files (const struct dirent *de) +{ + char name[1024]; + int number = -1; + sscanf(de->d_name, input_filter, &number); + sprintf(name, input_filter, number); + return !strcmp(name, de->d_name); +} + +static int ilog(unsigned _v){ + int ret; + for(ret=0;_v;ret++)_v>>=1; + return ret; +} + +int +main(int argc, char *argv[]) +{ + int c,long_option_index; + int i, n; + char *input_mask; + char *input_directory; + char *scratch; + th_comment tc; + struct dirent **png_files; + int soft_target=0; + int ret; + + while(1) { + + c=getopt_long(argc,argv,optstring,options,&long_option_index); + if(c == EOF) + break; + + switch(c) { + case 'h': + usage(); + break; + case 'o': + option_output = optarg; + break;; + case 'v': + video_quality=rint(atof(optarg)*6.3); + if(video_quality<0 || video_quality>63){ + fprintf(stderr,"Illegal video quality (choose 0 through 10)\n"); + exit(1); + } + video_rate=0; + break; + case 'V': + video_rate=rint(atof(optarg)*1000); + if(video_rate<1){ + fprintf(stderr,"Illegal video bitrate (choose > 0 please)\n"); + exit(1); + } + video_quality=0; + break; + case '\1': + soft_target=1; + break; + case 'c': + vp3_compatible=1; + break; + case 'k': + keyframe_frequency=rint(atof(optarg)); + if(keyframe_frequency<1 || keyframe_frequency>2147483647){ + fprintf(stderr,"Illegal keyframe frequency\n"); + exit(1); + } + break; + + case 'd': + buf_delay=atoi(optarg); + if(buf_delay<=0){ + fprintf(stderr,"Illegal buffer delay\n"); + exit(1); + } + break; + case 's': + video_aspect_numerator=rint(atof(optarg)); + break; + case 'S': + video_aspect_denominator=rint(atof(optarg)); + break; + case 'f': + video_fps_numerator=rint(atof(optarg)); + break; + case 'F': + video_fps_denominator=rint(atof(optarg)); + break; + case '\5': + chroma_format=TH_PF_444; + break; + case '\6': + chroma_format=TH_PF_422; + break; + case '\2': + twopass=3; /* perform both passes */ + twopass_file=tmpfile(); + if(!twopass_file){ + fprintf(stderr,"Unable to open temporary file for twopass data\n"); + exit(1); + } + break; + case '\3': + twopass=1; /* perform first pass */ + twopass_file=fopen(optarg,"wb"); + if(!twopass_file){ + fprintf(stderr,"Unable to open \'%s\' for twopass data\n",optarg); + exit(1); + } + break; + case '\4': + twopass=2; /* perform second pass */ + twopass_file=fopen(optarg,"rb"); + if(!twopass_file){ + fprintf(stderr,"Unable to open twopass data file \'%s\'",optarg); + exit(1); + } + break; + default: + usage(); + break; + } + } + + if(argc < 3) { + usage(); + } + + if(soft_target){ + if(video_rate<=0){ + fprintf(stderr,"Soft rate target (--soft-target) requested without a bitrate (-V).\n"); + exit(1); + } + if(video_quality==-1) + video_quality=0; + }else{ + if(video_rate>0) + video_quality=0; + if(video_quality==-1) + video_quality=48; + } + + if(keyframe_frequency<=0){ + /*Use a default keyframe frequency of 64 for 1-pass (streaming) mode, and + 256 for two-pass mode.*/ + keyframe_frequency=twopass?256:64; + } + + input_mask = argv[optind]; + if (!input_mask) { + fprintf(stderr, "no input files specified; run with -h for help.\n"); + exit(1); + } + /* dirname and basename must operate on scratch strings */ + scratch = strdup(input_mask); + input_directory = strdup(dirname(scratch)); + free(scratch); + scratch = strdup(input_mask); + input_filter = strdup(basename(scratch)); + free(scratch); + +#ifdef DEBUG + fprintf(stderr, "scanning %s with filter '%s'\n", + input_directory, input_filter); +#endif + n = scandir (input_directory, &png_files, include_files, alphasort); + + if (!n) { + fprintf(stderr, "no input files found; run with -h for help.\n"); + exit(1); + } + + ogg_fp = fopen(option_output, "wb"); + if(!ogg_fp) { + fprintf(stderr, "%s: error: %s\n", + option_output, "couldn't open output file"); + return 1; + } + + srand(time(NULL)); + if(ogg_stream_init(&ogg_os, rand())) { + fprintf(stderr, "%s: error: %s\n", + option_output, "couldn't create ogg stream state"); + return 1; + } + + for(passno=(twopass==3?1:twopass);passno<=(twopass==3?2:twopass);passno++){ + unsigned int w; + unsigned int h; + unsigned char *yuv; + char input_png[1024]; + int last = 0; + + snprintf(input_png, 1023,"%s/%s", input_directory, png_files[0]->d_name); + if(png_read(input_png, &w, &h, &yuv)) { + fprintf(stderr, "could not read %s\n", input_png); + exit(1); + } + + if (passno!=2) fprintf(stderr,"%d frames, %dx%d\n",n,w,h); + + /* setup complete. Raw processing loop */ + switch(passno){ + case 0: case 2: + fprintf(stderr,"\rCompressing.... \n"); + break; + case 1: + fprintf(stderr,"\rScanning first pass.... \n"); + break; + } + + fprintf(stderr, "%s\n", input_png); + + th_info_init(&ti); + ti.frame_width = ((w + 15) >>4)<<4; + ti.frame_height = ((h + 15)>>4)<<4; + ti.pic_width = w; + ti.pic_height = h; + ti.pic_x = 0; + ti.pic_y = 0; + ti.fps_numerator = video_fps_numerator; + ti.fps_denominator = video_fps_denominator; + ti.aspect_numerator = video_aspect_numerator; + ti.aspect_denominator = video_aspect_denominator; + ti.colorspace = TH_CS_UNSPECIFIED; + ti.pixel_fmt = chroma_format; + ti.target_bitrate = video_rate; + ti.quality = video_quality; + ti.keyframe_granule_shift=ilog(keyframe_frequency-1); + + td=th_encode_alloc(&ti); + th_info_clear(&ti); + /* setting just the granule shift only allows power-of-two keyframe + spacing. Set the actual requested spacing. */ + ret=th_encode_ctl(td,TH_ENCCTL_SET_KEYFRAME_FREQUENCY_FORCE, + &keyframe_frequency,sizeof(keyframe_frequency-1)); + if(ret<0){ + fprintf(stderr,"Could not set keyframe interval to %d.\n",(int)keyframe_frequency); + } + if(vp3_compatible){ + ret=th_encode_ctl(td,TH_ENCCTL_SET_VP3_COMPATIBLE,&vp3_compatible, + sizeof(vp3_compatible)); + if(ret<0||!vp3_compatible){ + fprintf(stderr,"Could not enable strict VP3 compatibility.\n"); + if(ret>=0){ + fprintf(stderr,"Ensure your source format is supported by VP3.\n"); + fprintf(stderr, + "(4:2:0 pixel format, width and height multiples of 16).\n"); + } + } + } + if(soft_target){ + /* reverse the rate control flags to favor a 'long time' strategy */ + int arg = TH_RATECTL_CAP_UNDERFLOW; + ret=th_encode_ctl(td,TH_ENCCTL_SET_RATE_FLAGS,&arg,sizeof(arg)); + if(ret<0) + fprintf(stderr,"Could not set encoder flags for --soft-target\n"); + /* Default buffer control is overridden on two-pass */ + if(!twopass&&buf_delay<0){ + if((keyframe_frequency*7>>1) > 5*video_fps_numerator/video_fps_denominator) + arg=keyframe_frequency*7>>1; + else + arg=5*video_fps_numerator/video_fps_denominator; + ret=th_encode_ctl(td,TH_ENCCTL_SET_RATE_BUFFER,&arg,sizeof(arg)); + if(ret<0) + fprintf(stderr,"Could not set rate control buffer for --soft-target\n"); + } + } + /* set up two-pass if needed */ + if(passno==1){ + unsigned char *buffer; + int bytes; + bytes=th_encode_ctl(td,TH_ENCCTL_2PASS_OUT,&buffer,sizeof(buffer)); + if(bytes<0){ + fprintf(stderr,"Could not set up the first pass of two-pass mode.\n"); + fprintf(stderr,"Did you remember to specify an estimated bitrate?\n"); + exit(1); + } + /*Perform a seek test to ensure we can overwrite this placeholder data at + the end; this is better than letting the user sit through a whole + encode only to find out their pass 1 file is useless at the end.*/ + if(fseek(twopass_file,0,SEEK_SET)<0){ + fprintf(stderr,"Unable to seek in two-pass data file.\n"); + exit(1); + } + if(fwrite(buffer,1,bytes,twopass_file)<bytes){ + fprintf(stderr,"Unable to write to two-pass data file.\n"); + exit(1); + } + fflush(twopass_file); + } + if(passno==2){ + /*Enable the second pass here. + We make this call just to set the encoder into 2-pass mode, because + by default enabling two-pass sets the buffer delay to the whole file + (because there's no way to explicitly request that behavior). + If we waited until we were actually encoding, it would overwite our + settings.*/ + if(th_encode_ctl(td,TH_ENCCTL_2PASS_IN,NULL,0)<0){ + fprintf(stderr,"Could not set up the second pass of two-pass mode.\n"); + exit(1); + } + if(twopass==3){ + if(fseek(twopass_file,0,SEEK_SET)<0){ + fprintf(stderr,"Unable to seek in two-pass data file.\n"); + exit(1); + } + } + } + /*Now we can set the buffer delay if the user requested a non-default one + (this has to be done after two-pass is enabled).*/ + if(passno!=1&&buf_delay>=0){ + ret=th_encode_ctl(td,TH_ENCCTL_SET_RATE_BUFFER, + &buf_delay,sizeof(buf_delay)); + if(ret<0){ + fprintf(stderr,"Warning: could not set desired buffer delay.\n"); + } + } + /* write the bitstream header packets with proper page interleave */ + th_comment_init(&tc); + /* first packet will get its own page automatically */ + if(th_encode_flushheader(td,&tc,&op)<=0){ + fprintf(stderr,"Internal Theora library error.\n"); + exit(1); + } + th_comment_clear(&tc); + if(passno!=1){ + ogg_stream_packetin(&ogg_os,&op); + if(ogg_stream_pageout(&ogg_os,&og)!=1){ + fprintf(stderr,"Internal Ogg library error.\n"); + exit(1); + } + fwrite(og.header,1,og.header_len,ogg_fp); + fwrite(og.body,1,og.body_len,ogg_fp); + } + /* create the remaining theora headers */ + for(;;){ + ret=th_encode_flushheader(td,&tc,&op); + if(ret<0){ + fprintf(stderr,"Internal Theora library error.\n"); + exit(1); + } + else if(!ret)break; + if(passno!=1)ogg_stream_packetin(&ogg_os,&op); + } + /* Flush the rest of our headers. This ensures + the actual data in each stream will start + on a new page, as per spec. */ + if(passno!=1){ + for(;;){ + int result = ogg_stream_flush(&ogg_os,&og); + if(result<0){ + /* can't get here */ + fprintf(stderr,"Internal Ogg library error.\n"); + exit(1); + } + if(result==0)break; + fwrite(og.header,1,og.header_len,ogg_fp); + fwrite(og.body,1,og.body_len,ogg_fp); + } + } + + i=0; last=0; + do { + if(i >= n-1) last = 1; + if(theora_write_frame(w, h, yuv, last)) { + fprintf(stderr,"Encoding error.\n"); + exit(1); + } + free(yuv); + i++; + if (!last) { + snprintf(input_png, 1023,"%s/%s", input_directory, png_files[i]->d_name); + if(png_read(input_png, &w, &h, &yuv)) { + fprintf(stderr, "could not read %s\n", input_png); + exit(1); + } + fprintf(stderr, "%s\n", input_png); + } + } while (!last); + + if(passno==1){ + /* need to read the final (summary) packet */ + unsigned char *buffer; + int bytes = th_encode_ctl(td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer)); + if(bytes<0){ + fprintf(stderr,"Could not read two-pass summary data from encoder.\n"); + exit(1); + } + if(fseek(twopass_file,0,SEEK_SET)<0){ + fprintf(stderr,"Unable to seek in two-pass data file.\n"); + exit(1); + } + if(fwrite(buffer,1,bytes,twopass_file)<bytes){ + fprintf(stderr,"Unable to write to two-pass data file.\n"); + exit(1); + } + fflush(twopass_file); + } + th_encode_free(td); + } + + if(ogg_stream_flush(&ogg_os, &og)) { + fwrite(og.header, og.header_len, 1, ogg_fp); + fwrite(og.body, og.body_len, 1, ogg_fp); + } + + free(input_directory); + free(input_filter); + + while (n--) free(png_files[n]); + free(png_files); + + if(ogg_fp){ + fflush(ogg_fp); + if(ogg_fp!=stdout)fclose(ogg_fp); + } + + ogg_stream_clear(&ogg_os); + if(twopass_file)fclose(twopass_file); + fprintf(stderr,"\r \ndone.\n\n"); + + return 0; +} |