pax_global_header00006660000000000000000000000064126147160530014517gustar00rootroot0000000000000052 comment=db1f79f409818fa0476ecf8593079a7ca3dbafd2 tstools-1.13~git20151030/000077500000000000000000000000001261471605300146705ustar00rootroot00000000000000tstools-1.13~git20151030/.gitignore000077500000000000000000000000311261471605300166550ustar00rootroot00000000000000/bin/ /lib/ /obj/ /w32/ tstools-1.13~git20151030/Makefile000066400000000000000000000465411261471605300163420ustar00rootroot00000000000000# Makefile for the H.264 Elementary Stream software # - temporarily hacked to work on Mac OS/X 10.5 (Leopard) # # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Amino Communications Ltd, Swavesey, Cambridge UK # # ***** END LICENSE BLOCK ***** # ### RUN WITH GNU MAKE # Gnu make recommends always setting some standard variables SHELL = /bin/sh # And re-establishing the required suffix list .SUFFIXES: .SUFFIXES: .c .o # GNU conventional destination vars prefix=/usr/local exec_prefix=$(prefix) bindir=$(exec_prefix)/bin libdir=$(exec_prefix)/lib mandir=/usr/local/man man1dir=$(mandir)/man1 manext=.1 INSTALL=install INSTALL_PROGRAM=$(INSTALL) -m 0555 -s INSTALL_LIB=$(INSTALL) -m 0444 -s INSTALL_DATA=$(INSTALL) -m 0444 TSTOOLS_VERSION=1.13 TSTOOLS_LIB_VERSION=1 ifdef CROSS_COMPILE CC = $(CROSS_COMPILE)gcc else CC = gcc endif # Use WARN=1 periodically to get too many warnings... ifdef WARN WARNING_FLAGS = -Wall -W -Wfloat-equal -Wundef -Wshadow -Wpointer-arith -Wcast-qual -Wconversion -Wmissing-prototypes -Wmissing-declarations -Wunreachable-code -Winline else WARNING_FLAGS = -Wall endif # Use NOOPT=1 if using valgrind --tool=memcheck/addrecheck ifdef NOOPT OPTIMISE_FLAGS = -g else OPTIMISE_FLAGS = -O2 -g endif # Use PROFILE=1 to allow use of gprof (but this is *not* needed for valgrind) ifdef PROFILE PROFILE_FLAGS = -pg else PROFILE_FLAGS = endif # On Linux, large file support is not necessarily enabled. To make programs # assume large file support, it is necessary to build them with _FILE_OFFSET_BITS=64. # This replaces the "standard" short file operations with equivalent large file # operations. # On (Free)BSD, this is not necessary, but conversely it does not look like defining # the flags will have any effect either. LFS_FLAGS = -D_FILE_OFFSET_BITS=64 # Try for a best guess whether this is a Mac running OS/X, or some other # sort of thing (presumably Linux or BSD) ifeq ($(shell uname -s), Darwin) SYSTEM = "macosx" ARCH_FLAGS = # If you're still building on a version of Mac OS X that supports powerpc, # then you may want to uncomment the next line. Obviously, this no longer # works in Lion, which doesn't support powerpc machines any more. #ARCH_FLAGS = -arch ppc -arch i386 else SYSTEM = "other" ARCH_FLAGS = -fPIC endif CFLAGS += $(WARNING_FLAGS) $(OPTIMISE_FLAGS) $(LFS_FLAGS) -I. $(PROFILE_FLAGS) $(ARCH_FLAGS) -DTSTOOLS_VERSION=$(TSTOOLS_VERSION) LDFLAGS += -g $(PROFILE_FLAGS) $(ARCH_FLAGS) -lm # Target directories OBJDIR = obj LIBDIR = lib BINDIR = bin MANDIR = docs/mdoc # All of our non-program object modules OBJS = \ $(OBJDIR)/accessunit.o \ $(OBJDIR)/avs.o \ $(OBJDIR)/ac3.o \ $(OBJDIR)/adts.o \ $(OBJDIR)/bitdata.o \ $(OBJDIR)/es.o \ $(OBJDIR)/filter.o \ $(OBJDIR)/fmtx.o \ $(OBJDIR)/h222.o \ $(OBJDIR)/h262.o \ $(OBJDIR)/audio.o \ $(OBJDIR)/l2audio.o \ $(OBJDIR)/misc.o \ $(OBJDIR)/nalunit.o \ $(OBJDIR)/ps.o \ $(OBJDIR)/pes.o \ $(OBJDIR)/pidint.o \ $(OBJDIR)/printing.o \ $(OBJDIR)/reverse.o \ $(OBJDIR)/ts.o \ $(OBJDIR)/tsplay_innards.o \ $(OBJDIR)/tswrite.o \ $(OBJDIR)/pcap.o \ $(OBJDIR)/ethernet.o \ $(OBJDIR)/ipv4.o # Our program object modules PROG_OBJS = \ $(OBJDIR)/es2ts.o \ $(OBJDIR)/esdots.o \ $(OBJDIR)/esfilter.o \ $(OBJDIR)/esmerge.o \ $(OBJDIR)/esreport.o \ $(OBJDIR)/esreverse.o \ $(OBJDIR)/ps2ts.o \ $(OBJDIR)/psreport.o \ $(OBJDIR)/psdots.o \ $(OBJDIR)/stream_type.o \ $(OBJDIR)/ts2es.o \ $(OBJDIR)/tsdvbsub.o \ $(OBJDIR)/tsinfo.o \ $(OBJDIR)/tsplay.o \ $(OBJDIR)/tsreport.o \ $(OBJDIR)/tsserve.o \ $(OBJDIR)/ts_packet_insert.o \ $(OBJDIR)/m2ts2ts.o \ $(OBJDIR)/pcapreport.o \ $(OBJDIR)/tsfilter.o TS2PS_OBJS = $(OBJDIR)/ts2ps.o TEST_PES_OBJS = $(OBJDIR)/test_pes.o TEST_PRINTING_OBJS = $(OBJDIR)/test_printing.o TEST_OBJS = \ $(OBJDIR)/test_nal_unit_list.o \ $(OBJDIR)/test_es_unit_list.o # Our library STATIC_LIB = $(LIBDIR)/libtstools.a LIBOPTS = $(ARCH_FLAGS) $(STATIC_LIB) ifeq ($(shell uname -s), Darwin) SHARED_LIB_NAME = libtstools.xxx else SHARED_LIB_NAME = libtstools.so endif SHARED_LIB = $(LIBDIR)/$(SHARED_LIB_NAME) # All of our programs (except the testing ones) PROGS = \ $(BINDIR)/esfilter \ $(BINDIR)/ts2es \ $(BINDIR)/es2ts \ $(BINDIR)/esdots \ $(BINDIR)/esmerge \ $(BINDIR)/esreport \ $(BINDIR)/esreverse \ $(BINDIR)/ps2ts \ $(BINDIR)/psreport \ $(BINDIR)/psdots \ $(BINDIR)/stream_type \ $(BINDIR)/tsdvbsub \ $(BINDIR)/tsinfo \ $(BINDIR)/tsreport \ $(BINDIR)/tsplay \ $(BINDIR)/tsserve \ $(BINDIR)/ts_packet_insert \ $(BINDIR)/m2ts2ts \ $(BINDIR)/pcapreport \ $(BINDIR)/tsfilter \ $(BINDIR)/rtp2264 TS2PS_PROG = $(BINDIR)/ts2ps # Is test_pes still useful? TEST_PES_PROG = $(BINDIR)/test_pes TEST_PRINTING_PROG = $(BINDIR)/test_printing # And then the testing programs (which we only build if we are # running the tests) TEST_PROGS = test_nal_unit_list test_es_unit_list # ------------------------------------------------------------ all: $(BINDIR) $(LIBDIR) $(OBJDIR) $(PROGS) $(SHARED_LIB) # ts2ps is not yet an offical program, so for the moment build # it separately .PHONY: ts2ps ts2ps: $(TS2PS_PROG) ifeq ($(shell uname -s), Darwin) # Make libraries containing universal objects on Mac $(STATIC_LIB): $(OBJS) libtool -static $(OBJS) -o $(STATIC_LIB) $(SHARED_LIB): $(OBJS) libtool -dynamic $(OBJS) -o $(SHARED_LIB) else $(STATIC_LIB): $(OBJS) rm -f $(STATIC_LIB) ar rc $(STATIC_LIB) $(OBJS) $(SHARED_LIB): $(OBJS) $(LD) -shared -soname $(SHARED_LIB_NAME).$(TSTOOLS_LIB_VERSION) -o $(SHARED_LIB) $(OBJS) -lc -lm endif # Build all of the utilities with the static library, so that they can # be copied around, shared, etc., without having to think about it $(BINDIR)/esfilter: $(OBJDIR)/esfilter.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/ts2es: $(OBJDIR)/ts2es.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/es2ts: $(OBJDIR)/es2ts.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/esdots: $(OBJDIR)/esdots.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/esmerge: $(OBJDIR)/esmerge.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/esreport: $(OBJDIR)/esreport.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/esreverse: $(OBJDIR)/esreverse.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/stream_type: $(OBJDIR)/stream_type.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/psreport: $(OBJDIR)/psreport.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/psdots: $(OBJDIR)/psdots.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/ps2ts: $(OBJDIR)/ps2ts.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/tsinfo: $(OBJDIR)/tsinfo.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/tsreport: $(OBJDIR)/tsreport.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/tsserve: $(OBJDIR)/tsserve.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/tsplay: $(OBJDIR)/tsplay.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/ts_packet_insert: $(OBJDIR)/ts_packet_insert.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/m2ts2ts: $(OBJDIR)/m2ts2ts.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/pcapreport: $(OBJDIR)/pcapreport.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/tsfilter: $(OBJDIR)/tsfilter.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/tsdvbsub: $(OBJDIR)/tsdvbsub.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/rtp2264: $(OBJDIR)/rtp2264.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) # Not installed $(BINDIR)/ts2ps: $(OBJDIR)/ts2ps.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/test_pes: $(OBJDIR)/test_pes.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/test_printing: $(OBJDIR)/test_printing.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/test_nal_unit_list: $(OBJDIR)/test_nal_unit_list.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) $(BINDIR)/test_es_unit_list: $(OBJDIR)/test_es_unit_list.o $(STATIC_LIB) $(CC) $< -o $@ $(LIBOPTS) $(LDFLAGS) # Some header files depend upon others, so including one requires # the others as well ES_H = es_fns.h es_defns.h h222_fns.h h222_defns.h TS_H = ts_fns.h ts_defns.h h222_fns.h h222_defns.h tswrite_fns.h \ tswrite_defns.h pidint_fns.h pidint_defns.h ACCESSUNIT_H = accessunit_fns.h accessunit_defns.h $(NALUNIT_H) NALUNIT_H = nalunit_fns.h nalunit_defns.h es_fns.h es_defns.h \ bitdata_fns.h bitdata_defns.h PES_H = pes_fns.h pes_defns.h PS_H = ps_fns.h ps_defns.h AVS_H = avs_fns.h avs_defns.h H262_H = h262_fns.h h262_defns.h TSWRITE_H = tswrite_fns.h tswrite_defns.h REVERSE_H = reverse_fns.h reverse_defns.h FILTER_H = filter_fns.h filter_defns.h $(REVERSE_H) AUDIO_H = adts_fns.h l2audio_fns.h ac3_fns.h audio_fns.h audio_defns.h adts_defns.h # Everyone depends upon the basic configuration file, and I assert they all # want (or may want) printing... $(OBJS) $(TEST_OBJS) $(PROG_OBJS): compat.h printing_fns.h # Which library modules depend on which header files is complex, so # lets just be simple $(OBJS): \ $(ACCESSUNIT_H) $(NALUNIT_H) $(TS_H) $(ES_H) $(PES_H) \ misc_fns.h printing_fns.h $(PS_H) $(H262_H) \ $(TSWRITE_H) $(AVS_H) $(REVERSE_H) $(FILTER_H) $(AUDIO_H) $(OBJDIR)/%.o: %.c $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/es2ts.o: es2ts.c $(ES_H) $(TS_H) misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/esdots.o: esdots.c misc_fns.h $(ACCESSUNIT_H) $(H262_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/esfilter.o: esfilter.c $(TS_H) misc_fns.h $(ACCESSUNIT_H) $(H262_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/esreport.o: esreport.c misc_fns.h $(ACCESSUNIT_H) $(H262_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/esmerge.o: esmerge.c misc_fns.h $(ACCESSUNIT_H) $(AUDIO_H) $(TSWRITE_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/esreverse.o: esreverse.c $(TS_H) $(REVERSE_H) misc_fns.h $(ACCESSUNIT_H) $(H262_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/fmtx.o: fmtx.c fmtx.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/psreport.o: psreport.c $(ES_H) $(PS_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/psdots.o: psdots.c $(ES_H) $(PS_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/ps2ts.o: ps2ts.c $(TS_H) misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/stream_type.o: stream_type.c $(ES_H) $(TS_H) $(NALUNIT_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/ts2es.o: ts2es.c $(TS_H) misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/ts2ps.o: ts2ps.c $(TS_H) $(PS_H) misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/tsdvbsub.o: tsdvbsub.c $(TS_H) misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/tsinfo.o: tsinfo.c $(TS_H) misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/tsreport.o: tsreport.c $(TS_H) fmtx.h misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/tsserve.o: tsserve.c $(TS_H) $(PS_H) $(ES_H) misc_fns.h $(PES_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/ts_packet_insert.o: ts_packet_insert.c $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/tsplay.o: tsplay.c $(TS_H) misc_fns.h $(PS_H) $(PES_H) version.h tsplay_fns.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/tswrite.o: tswrite.c misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/m2ts2ts.o: m2ts2ts.c $(TS_H) misc_fns.h version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/pcapreport.o: pcapreport.c pcap.h version.h misc_fns.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/tsfilter.o: tsfilter.c version.h misc_fns.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/test_pes.o: test_pes.c $(TS_H) $(PS_H) $(ES_H) misc_fns.h $(PES_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/test_printing.o: test_printing.c $(TS_H) $(PS_H) $(ES_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/test_nal_unit_list.o: test_nal_unit_list.c $(NALUNIT_H) version.h $(CC) -c $< -o $@ $(CFLAGS) $(OBJDIR)/test_es_unit_list.o: test_es_unit_list.c $(ES_H) version.h $(CC) -c $< -o $@ $(CFLAGS) # ------------------------------------------------------------ # Directory creation $(OBJDIR) $(LIBDIR) $(BINDIR) $(DESTDIR)$(bindir) $(DESTDIR)$(libdir) $(DESTDIR)$(man1dir): mkdir -p $@ # ------------------------------------------------------------ .PHONY: install-man install-man: $(DESTDIR)$(man1dir) $(INSTALL_DATA) $(MANDIR)/esfilter.1 $(DESTDIR)$(man1dir)/esfilter$(manext) $(INSTALL_DATA) $(MANDIR)/ts2es.1 $(DESTDIR)$(man1dir)/ts2es$(manext) $(INSTALL_DATA) $(MANDIR)/es2ts.1 $(DESTDIR)$(man1dir)/es2ts$(manext) $(INSTALL_DATA) $(MANDIR)/esdots.1 $(DESTDIR)$(man1dir)/esdots$(manext) $(INSTALL_DATA) $(MANDIR)/esmerge.1 $(DESTDIR)$(man1dir)/esmerge$(manext) $(INSTALL_DATA) $(MANDIR)/esreport.1 $(DESTDIR)$(man1dir)/esreport$(manext) $(INSTALL_DATA) $(MANDIR)/esreverse.1 $(DESTDIR)$(man1dir)/esreverse$(manext) $(INSTALL_DATA) $(MANDIR)/stream_type.1 $(DESTDIR)$(man1dir)/stream_type$(manext) $(INSTALL_DATA) $(MANDIR)/psreport.1 $(DESTDIR)$(man1dir)/psreport$(manext) $(INSTALL_DATA) $(MANDIR)/psdots.1 $(DESTDIR)$(man1dir)/psdots$(manext) $(INSTALL_DATA) $(MANDIR)/ps2ts.1 $(DESTDIR)$(man1dir)/ps2ts$(manext) $(INSTALL_DATA) $(MANDIR)/tsinfo.1 $(DESTDIR)$(man1dir)/tsinfo$(manext) $(INSTALL_DATA) $(MANDIR)/tsreport.1 $(DESTDIR)$(man1dir)/tsreport$(manext) $(INSTALL_DATA) $(MANDIR)/tsserve.1 $(DESTDIR)$(man1dir)/tsserve$(manext) $(INSTALL_DATA) $(MANDIR)/tsplay.1 $(DESTDIR)$(man1dir)/tsplay$(manext) $(INSTALL_DATA) $(MANDIR)/ts_packet_insert.1 $(DESTDIR)$(man1dir)/ts_packet_insert$(manext) $(INSTALL_DATA) $(MANDIR)/m2ts2ts.1 $(DESTDIR)$(man1dir)/m2ts2ts$(manext) $(INSTALL_DATA) $(MANDIR)/pcapreport.1 $(DESTDIR)$(man1dir)/pcapreport$(manext) $(INSTALL_DATA) $(MANDIR)/tsfilter.1 $(DESTDIR)$(man1dir)/tsfilter$(manext) $(INSTALL_DATA) $(MANDIR)/tsdvbsub.1 $(DESTDIR)$(man1dir)/tsdvbsub$(manext) $(INSTALL_DATA) $(MANDIR)/rtp2264.1 $(DESTDIR)$(man1dir)/rtp2264$(manext) .PHONY: uninstall-man uninstall-man: rm -f $(DESTDIR)$(man1dir)/esfilter$(manext) rm -f $(DESTDIR)$(man1dir)/ts2es$(manext) rm -f $(DESTDIR)$(man1dir)/es2ts$(manext) rm -f $(DESTDIR)$(man1dir)/esdots$(manext) rm -f $(DESTDIR)$(man1dir)/esmerge$(manext) rm -f $(DESTDIR)$(man1dir)/esreport$(manext) rm -f $(DESTDIR)$(man1dir)/esreverse$(manext) rm -f $(DESTDIR)$(man1dir)/stream_type$(manext) rm -f $(DESTDIR)$(man1dir)/psreport$(manext) rm -f $(DESTDIR)$(man1dir)/psdots$(manext) rm -f $(DESTDIR)$(man1dir)/ps2ts$(manext) rm -f $(DESTDIR)$(man1dir)/tsinfo$(manext) rm -f $(DESTDIR)$(man1dir)/tsreport$(manext) rm -f $(DESTDIR)$(man1dir)/tsserve$(manext) rm -f $(DESTDIR)$(man1dir)/tsplay$(manext) rm -f $(DESTDIR)$(man1dir)/ts_packet_insert$(manext) rm -f $(DESTDIR)$(man1dir)/m2ts2ts$(manext) rm -f $(DESTDIR)$(man1dir)/pcapreport$(manext) rm -f $(DESTDIR)$(man1dir)/tsfilter$(manext) rm -f $(DESTDIR)$(man1dir)/tsdvbsub$(manext) rm -f $(DESTDIR)$(man1dir)/rtp2264$(manext) # Shared lib not installed currently .PHONY: install-prog install-prog: all $(DESTDIR)$(bindir) $(DESTDIR)$(libdir) $(INSTALL_PROGRAM) $(BINDIR)/esfilter $(DESTDIR)$(bindir)/esfilter $(INSTALL_PROGRAM) $(BINDIR)/ts2es $(DESTDIR)$(bindir)/ts2es $(INSTALL_PROGRAM) $(BINDIR)/es2ts $(DESTDIR)$(bindir)/es2ts $(INSTALL_PROGRAM) $(BINDIR)/esdots $(DESTDIR)$(bindir)/esdots $(INSTALL_PROGRAM) $(BINDIR)/esmerge $(DESTDIR)$(bindir)/esmerge $(INSTALL_PROGRAM) $(BINDIR)/esreport $(DESTDIR)$(bindir)/esreport $(INSTALL_PROGRAM) $(BINDIR)/esreverse $(DESTDIR)$(bindir)/esreverse $(INSTALL_PROGRAM) $(BINDIR)/stream_type $(DESTDIR)$(bindir)/stream_type $(INSTALL_PROGRAM) $(BINDIR)/psreport $(DESTDIR)$(bindir)/psreport $(INSTALL_PROGRAM) $(BINDIR)/psdots $(DESTDIR)$(bindir)/psdots $(INSTALL_PROGRAM) $(BINDIR)/ps2ts $(DESTDIR)$(bindir)/ps2ts $(INSTALL_PROGRAM) $(BINDIR)/tsinfo $(DESTDIR)$(bindir)/tsinfo $(INSTALL_PROGRAM) $(BINDIR)/tsreport $(DESTDIR)$(bindir)/tsreport $(INSTALL_PROGRAM) $(BINDIR)/tsserve $(DESTDIR)$(bindir)/tsserve $(INSTALL_PROGRAM) $(BINDIR)/tsplay $(DESTDIR)$(bindir)/tsplay $(INSTALL_PROGRAM) $(BINDIR)/ts_packet_insert $(DESTDIR)$(bindir)/ts_packet_insert $(INSTALL_PROGRAM) $(BINDIR)/m2ts2ts $(DESTDIR)$(bindir)/m2ts2ts $(INSTALL_PROGRAM) $(BINDIR)/pcapreport $(DESTDIR)$(bindir)/pcapreport $(INSTALL_PROGRAM) $(BINDIR)/tsfilter $(DESTDIR)$(bindir)/tsfilter $(INSTALL_PROGRAM) $(BINDIR)/tsdvbsub $(DESTDIR)$(bindir)/tsdvbsub $(INSTALL_PROGRAM) $(BINDIR)/rtp2264 $(DESTDIR)$(bindir)/rtp2264 .PHONY: uninstall-prog uninstall-prog: rm -f $(DESTDIR)$(bindir)/esfilter rm -f $(DESTDIR)$(bindir)/ts2es rm -f $(DESTDIR)$(bindir)/es2ts rm -f $(DESTDIR)$(bindir)/esdots rm -f $(DESTDIR)$(bindir)/esmerge rm -f $(DESTDIR)$(bindir)/esreport rm -f $(DESTDIR)$(bindir)/esreverse rm -f $(DESTDIR)$(bindir)/stream_type rm -f $(DESTDIR)$(bindir)/psreport rm -f $(DESTDIR)$(bindir)/psdots rm -f $(DESTDIR)$(bindir)/ps2ts rm -f $(DESTDIR)$(bindir)/tsinfo rm -f $(DESTDIR)$(bindir)/tsreport rm -f $(DESTDIR)$(bindir)/tsserve rm -f $(DESTDIR)$(bindir)/tsplay rm -f $(DESTDIR)$(bindir)/ts_packet_insert rm -f $(DESTDIR)$(bindir)/m2ts2ts rm -f $(DESTDIR)$(bindir)/pcapreport rm -f $(DESTDIR)$(bindir)/tsfilter rm -f $(DESTDIR)$(bindir)/tsdvbsub rm -f $(DESTDIR)$(bindir)/rtp2264 .PHONY: install install: install-man install-prog .PHONY: uninstall uninstall: uninstall-man uninstall-prog .PHONY: objclean objclean: -rm -f $(OBJS) -rm -f $(TEST_OBJS) -rm -f $(TEST_PROGS) -rm -f $(TS2PS_OBJS) $(TS2PS_PROG) -rm -f $(TEST_PES_OBJS) $(TEST_PES_PROG) -rm -f $(TEST_PRINTING_OBJS) $(TEST_PRINTING_PROG) -rm -f ES_test3.ts es_test3.ts -rm -f ES_test2.264 es_test3.264 -rm -f es_test_a.ts es_test_a.264 -rm -f es_test_b.ts es_test_b.264 -rm -f *.core .PHONY: clean clean: objclean -rm -f $(PROGS) -rm -f $(STATIC_LIB) -rm -f $(SHARED_LIB) -rm -f $(PROG_OBJS) .PHONY: distclean distclean: clean -rm -rf $(OBJDIR) $(LIBDIR) $(BINDIR) rm -f debian/files debian/tstools.* rm -rf debian/tstools .PHONY: dist dist: distclean ln -snf `pwd` ../tstools-$(TSTOOLS_VERSION) tar czhf ../tstools-$(TSTOOLS_VERSION).tar.gz ../tstools-$(TSTOOLS_VERSION) .PHONY: dist-debian dist-debian: dist ln -snf tstools-$(TSTOOLS_VERSION).tar.gz ../tstools_$(TSTOOLS_VERSION).orig.tar.gz debuild -uc -us TESTDATAFILE = /data/video/CVBt_hp_trail.264 # Only build test_printing if explicitly asked to do so .PHONY: test_printing test_printing: $(BINDIR)/test_printing # Only build test_pes if explicitly asked to do so .PHONY: test_pes test_pes: $(BINDIR)/test_pes .PHONY: test test: test_lists .PHONY: test_lists test_lists: $(BINDIR)/test_nal_unit_list $(BINDIR)/test_es_unit_list @echo +++ Testing NAL unit lists $(BINDIR)//test_nal_unit_list @echo +++ Test succeeded @echo +++ Testing ES unit lists $(BINDIR)/test_es_unit_list @echo +++ Test succeeded tstools-1.13~git20151030/Makefile.w32000066400000000000000000000313201261471605300167410ustar00rootroot00000000000000# Makefile for use on Windows # # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Amino Communications Ltd, Swavesey, Cambridge UK # # ***** END LICENSE BLOCK ***** .SUFFIXES: !if "$(DEBUG)"=="" DEBUG=0 !endif DEBUG_OBJDIR=w32\debug PLAIN_OBJDIR=w32\obj !if $(DEBUG) OBJDIR=$(DEBUG_OBJDIR) COPT=/Od /Zi /MTd LOPT=/DEBUG /INCREMENTAL:NO /NOLOGO # /NODEFAULTLIB:CMT !else OBJDIR=$(PLAIN_OBJDIR) COPT=/O2b2 /MT /DNDEBUG LOPT=/INCREMENTAL:NO /NOLOGO # /NODEFAULTLIB:CMT !endif EXEDIR=w32\bin LIBDIR=$(OBJDIR) LIBFILE=$(LIBDIR)\libtstools.lib CC = cl /c /W3 /WX /GF /Fd$(OBJDIR)\ $(COPT) #CC=cl /nologo /c /WX /GF /Fd$(OBJDIR)\ $(COPT) .SUFFIXES: .c .cpp .obj .exe {$(OBJDIR)}.obj # ------------------------------------------------------------ # Rules .c{$(OBJDIR)}.obj:: $(CC) /Fo$(OBJDIR)\ $< {$(OBJDIR)}.obj{$(EXEDIR)}.exe: link /out:$@ $(LOPT) $< # ------------------------------------------------------------ # Things to build PROGS = \ $(EXEDIR)\es2ts.exe \ $(EXEDIR)\esdots.exe \ $(EXEDIR)\esfilter.exe \ $(EXEDIR)\esmerge.exe \ $(EXEDIR)\esreport.exe \ $(EXEDIR)\esreverse.exe \ $(EXEDIR)\m2ts2ts.exe \ $(EXEDIR)\pcapreport.exe \ $(EXEDIR)\ps2ts.exe \ $(EXEDIR)\psdots.exe \ $(EXEDIR)\psreport.exe \ $(EXEDIR)\stream_type.exe \ $(EXEDIR)\ts2es.exe \ $(EXEDIR)\ts_packet_insert.exe \ $(EXEDIR)\tsdvbsub.exe \ $(EXEDIR)\tsinfo.exe \ $(EXEDIR)\tsplay.exe \ $(EXEDIR)\tsreport.exe \ $(EXEDIR)\tsserve.exe # Object files for the library LIB_OBJS = \ $(OBJDIR)\accessunit.obj \ $(OBJDIR)\ac3.obj \ $(OBJDIR)\adts.obj \ $(OBJDIR)\avs.obj \ $(OBJDIR)\audio.obj \ $(OBJDIR)\bitdata.obj \ $(OBJDIR)\es.obj \ $(OBJDIR)\ethernet.obj \ $(OBJDIR)\filter.obj \ $(OBJDIR)\fmtx.obj \ $(OBJDIR)\h222.obj \ $(OBJDIR)\h262.obj \ $(OBJDIR)\ipv4.obj \ $(OBJDIR)\l2audio.obj \ $(OBJDIR)\misc.obj \ $(OBJDIR)\nalunit.obj \ $(OBJDIR)\pcap.obj \ $(OBJDIR)\pes.obj \ $(OBJDIR)\pidint.obj \ $(OBJDIR)\printing.obj \ $(OBJDIR)\ps.obj \ $(OBJDIR)\reverse.obj \ $(OBJDIR)\ts.obj \ $(OBJDIR)\tsplay_innards.obj \ $(OBJDIR)\tswrite.obj # Object files for the programs PROG_OBJS = \ $(OBJDIR)/es2ts.obj \ $(OBJDIR)/esdots.obj \ $(OBJDIR)/esfilter.obj \ $(OBJDIR)/esmerge.obj \ $(OBJDIR)/esreport.obj \ $(OBJDIR)/esreverse.obj \ $(OBJDIR)/m2ts2ts.obj \ $(OBJDIR)/pcapreport.obj \ $(OBJDIR)/ps2ts.obj \ $(OBJDIR)/psdots.obj \ $(OBJDIR)/psreport.obj \ $(OBJDIR)/stream_type.obj \ $(OBJDIR)/ts2es.obj \ $(OBJDIR)/ts_packet_insert.obj \ $(OBJDIR)\tsdvbsub.obj \ $(OBJDIR)/tsinfo.obj \ $(OBJDIR)/tsplay.obj \ $(OBJDIR)/tsreport.obj \ $(OBJDIR)/tsserve.obj # ------------------------------------------------------------ # Targets all: $(OBJDIR) $(EXEDIR) $(LIBDIR) $(PROGS) ac3_fns.h: audio_fns.h accessunit_defns.h: nalunit_defns.h es_defns.h accessunit_fns.h: accessunit_defns.h adts_defns.h: audio_defns.h adts_fns.h: adts_defns.h audio_fns.h audio_defns.h: h222_defns.h audio_fns.h: audio_defns.h avs_defns.h: compat.h es_defns.h ts_defns.h reverse_defns.h avs_fns.h: avs_defns.h bitdata_defns.h: compat.h bitdata_fns.h: bitdata_defns.h es_defns.h: compat.h pes_defns.h es_fns.h: es_defns.h ethernet.h: compat.h pcap.h filter_defns.h: compat.h es_defns.h h262_defns.h accessunit_defns.h reverse_defns.h filter_fns.h: filter_defns.h h222_defns.h: h222_fns.h h262_defns.h: compat.h es_defns.h ts_defns.h h262_fns.h: h262_defns.h ipv4.h: compat.h l2audio_fns.h: audio_defns.h misc_defns.h: tswrite_defns.h video_defns.h misc_fns.h: misc_defns.h es_defns.h compat.h nalunit_defns.h: compat.h es_defns.h bitdata_defns.h nalunit_fns.h: nalunit_defns.h pcap.h: compat.h pes_defns.h: compat.h pidint_defns.h ps_defns.h ts_defns.h tswrite_defns.h pes_fns.h: pes_defns.h es_defns.h pidint_defns.h: compat.h pidint_fns.h: pidint_defns.h printing_fns.h: printing_defns.h ps_defns.h: compat.h h222_defns.h tswrite_defns.h ps_fns.h: compat.h h222_defns.h tswrite_defns.h ps_defns.h reverse_defns.h: compat.h es_defns.h h262_defns.h accessunit_defns.h reverse_fns.h: accessunit_defns.h reverse_defns.h h262_defns.h ts_defns.h: compat.h ts_fns.h: compat.h h222_defns.h tswrite_defns.h pidint_defns.h ts_defns.h tsplay_fns.h: tswrite_defns.h tsplay_defns.h tswrite_defns.h: compat.h ts_defns.h h222_defns.h tswrite_fns.h: tswrite_defns.h version.h: printing_fns.h video_defns.h: h222_defns.h $(OBJDIR)\ac3.obj: compat.h printing_fns.h misc_fns.h ac3_fns.h $(OBJDIR)\accessunit.obj: compat.h printing_fns.h es_fns.h ts_fns.h nalunit_fns.h accessunit_fns.h reverse_fns.h $(OBJDIR)\adts.obj: compat.h printing_fns.h misc_fns.h adts_fns.h $(OBJDIR)\audio.obj: compat.h printing_fns.h audio_fns.h adts_fns.h l2audio_fns.h ac3_fns.h $(OBJDIR)\avs.obj: compat.h printing_fns.h avs_fns.h es_fns.h ts_fns.h reverse_fns.h misc_fns.h $(OBJDIR)\bitdata.obj: compat.h bitdata_fns.h printing_fns.h $(OBJDIR)\es.obj: compat.h printing_fns.h misc_fns.h pes_fns.h tswrite_fns.h es_fns.h printing_fns.h $(OBJDIR)\es2ts.obj: compat.h es_fns.h ts_fns.h tswrite_fns.h misc_fns.h printing_fns.h version.h $(OBJDIR)\esdots.obj: compat.h es_fns.h pes_fns.h accessunit_fns.h h262_fns.h avs_fns.h printing_fns.h misc_fns.h version.h $(OBJDIR)\esfilter.obj: compat.h es_fns.h pes_fns.h nalunit_fns.h ts_fns.h accessunit_fns.h h262_fns.h misc_fns.h printing_fns.h tswrite_fns.h filter_fns.h version.h $(OBJDIR)\esmerge.obj: compat.h es_fns.h accessunit_fns.h avs_fns.h audio_fns.h ts_fns.h tswrite_fns.h misc_fns.h printing_fns.h version.h pes_fns.h $(OBJDIR)\esreport.obj: compat.h es_fns.h nalunit_fns.h ts_fns.h pes_fns.h accessunit_fns.h h262_fns.h avs_fns.h misc_fns.h printing_fns.h version.h $(OBJDIR)\esreverse.obj: compat.h es_fns.h nalunit_fns.h accessunit_fns.h h262_fns.h ts_fns.h tswrite_fns.h pes_fns.h reverse_fns.h misc_fns.h printing_fns.h version.h $(OBJDIR)\ethernet.obj: ethernet.h misc_fns.h $(OBJDIR)\filter.obj: compat.h es_fns.h ts_fns.h accessunit_fns.h h262_fns.h misc_fns.h printing_fns.h filter_fns.h $(OBJDIR)\fmtx.obj: compat.h fmtx.h $(OBJDIR)\h222.obj: h222_fns.h $(OBJDIR)\h262.obj: compat.h printing_fns.h h262_fns.h es_fns.h ts_fns.h reverse_fns.h misc_fns.h $(OBJDIR)\ipv4.obj: ipv4.h misc_fns.h $(OBJDIR)\l2audio.obj: compat.h misc_fns.h printing_fns.h l2audio_fns.h $(OBJDIR)\m2ts2ts.obj: compat.h ts_defns.h misc_fns.h printing_fns.h version.h $(OBJDIR)\misc.obj: compat.h misc_fns.h es_fns.h pes_fns.h printing_fns.h $(OBJDIR)\nalunit.obj: compat.h printing_fns.h es_fns.h ts_fns.h bitdata_fns.h nalunit_fns.h misc_fns.h printing_fns.h $(OBJDIR)\pcap.obj: pcap.h misc_fns.h $(OBJDIR)\pcapreport.obj: compat.h pcap.h ethernet.h ipv4.h version.h misc_fns.h ts_fns.h fmtx.h $(OBJDIR)\pes.obj: compat.h ts_fns.h ps_fns.h es_fns.h pes_fns.h pidint_fns.h h262_fns.h tswrite_fns.h printing_fns.h misc_fns.h $(OBJDIR)\pidint.obj: compat.h pidint_fns.h misc_fns.h printing_fns.h ts_fns.h h222_defns.h $(OBJDIR)\printing.obj: compat.h printing_fns.h $(OBJDIR)\ps.obj: compat.h ps_fns.h ts_fns.h pes_fns.h pidint_fns.h misc_fns.h printing_fns.h $(OBJDIR)\ps2ts.obj: compat.h pes_fns.h ps_fns.h ts_fns.h tswrite_fns.h misc_fns.h printing_fns.h version.h $(OBJDIR)\psdots.obj: compat.h ps_fns.h misc_fns.h printing_fns.h version.h $(OBJDIR)\psreport.obj: compat.h ps_fns.h pes_fns.h misc_fns.h printing_fns.h version.h $(OBJDIR)\reverse.obj: compat.h misc_defns.h printing_fns.h es_fns.h h262_fns.h nalunit_fns.h accessunit_fns.h ts_fns.h tswrite_fns.h reverse_fns.h $(OBJDIR)\stream_type.obj: compat.h es_fns.h ts_fns.h nalunit_fns.h h262_fns.h misc_fns.h printing_fns.h version.h $(OBJDIR)\test_es_unit_list.obj: compat.h es_fns.h $(OBJDIR)\test_nal_unit_list.obj: compat.h nalunit_fns.h $(OBJDIR)\test_pes.obj: compat.h pes_fns.h pidint_fns.h misc_fns.h ps_fns.h ts_fns.h es_fns.h h262_fns.h tswrite_fns.h version.h $(OBJDIR)\test_printing.obj: printing_fns.h version.h $(OBJDIR)\ts.obj: compat.h ts_fns.h tswrite_fns.h misc_fns.h printing_fns.h pidint_fns.h pes_fns.h $(OBJDIR)\ts2es.obj: compat.h ts_fns.h misc_fns.h printing_fns.h pidint_fns.h es_fns.h pes_fns.h version.h $(OBJDIR)\ts2ps.obj: compat.h ps_fns.h ts_fns.h misc_fns.h printing_fns.h pidint_fns.h pes_fns.h version.h $(OBJDIR)\ts_packet_insert.obj: compat.h misc_fns.h printing_fns.h version.h $(OBJDIR)\tsdvbsub.obj: compat.h ts_fns.h misc_fns.h printing_fns.h pidint_fns.h es_fns.h pes_fns.h version.h fmtx.h $(OBJDIR)\tsfilter.obj: compat.h ts_fns.h misc_fns.h printing_fns.h pidint_fns.h version.h tswrite_defns.h tswrite_fns.h $(OBJDIR)\tsinfo.obj: compat.h ts_fns.h misc_fns.h printing_fns.h pidint_fns.h version.h $(OBJDIR)\tsplay.obj: compat.h printing_fns.h tsplay_fns.h tswrite_fns.h printing_fns.h misc_fns.h version.h ps_fns.h pes_fns.h pidint_fns.h $(OBJDIR)\tsplay_innards.obj: compat.h printing_fns.h ts_fns.h ps_fns.h pes_fns.h misc_fns.h printing_fns.h tsplay_fns.h tswrite_fns.h pidint_fns.h $(OBJDIR)\tsreport.obj: compat.h ts_fns.h pes_fns.h misc_fns.h printing_fns.h pidint_fns.h fmtx.h version.h $(OBJDIR)\tsserve.obj: compat.h ts_fns.h ps_fns.h pes_fns.h accessunit_fns.h nalunit_fns.h misc_fns.h printing_fns.h tswrite_fns.h es_fns.h h262_fns.h filter_fns.h reverse_fns.h version.h $(OBJDIR)\tswrite.obj: compat.h misc_fns.h printing_fns.h tswrite_fns.h $(LIBFILE): $(LIBDIR) $(LIB_OBJS) lib /nologo /out:$@ $(LIB_OBJS) $(EXEDIR)\esfilter.exe: $(OBJDIR)\esfilter.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\esmerge.exe: $(OBJDIR)\esmerge.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\ts2es.exe: $(OBJDIR)\ts2es.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\es2ts.exe: $(OBJDIR)\es2ts.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\esdots.exe: $(OBJDIR)\esdots.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\esreport.exe: $(OBJDIR)\esreport.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\esreverse.exe: $(OBJDIR)\esreverse.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\m2ts2ts.exe: $(OBJDIR)\m2ts2ts.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\stream_type.exe: $(OBJDIR)\stream_type.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\pcapreport.exe: $(OBJDIR)\pcapreport.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\psreport.exe: $(OBJDIR)\psreport.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\ps2ts.exe: $(OBJDIR)\ps2ts.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\psdots.exe: $(OBJDIR)\psdots.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\ts_packet_insert.exe: $(OBJDIR)\ts_packet_insert.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\tsdvbsub.exe: $(OBJDIR)\tsdvbsub.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\tsinfo.exe: $(OBJDIR)\tsinfo.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\tsreport.exe: $(OBJDIR)\tsreport.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\tsplay.exe: $(OBJDIR)\tsplay.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib $(EXEDIR)\tsserve.exe: $(OBJDIR)\tsserve.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib # For the moment, ts2ps is not an official program, so must # be built separately ts2ps: $(EXEDIR)\ts2ps.exe $(EXEDIR)\ts2ps.exe: $(OBJDIR)\ts2ps.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib # Only build test_pes if explicitly asked to do so test_pes: $(EXEDIR)\test_pes.exe $(EXEDIR)\test_pes.exe: $(OBJDIR)\test_pes.obj $(LIBFILE) link /out:$@ $(LOPT) $** wsock32.lib # ------------------------------------------------------------ # Directories $(OBJDIR): $(COMSPEC) /x /c md $(OBJDIR) !if "$(LIBDIR)" != "$(OBJDIR)" $(LIBDIR): $(COMSPEC) /x /c md $(LIBDIR) !endif !if "$(EXEDIR)" != "$(OBJDIR)" $(EXEDIR): $(COMSPEC) /x /c md $(EXEDIR) !endif # ------------------------------------------------------------ # Tidying up clean: -del /q $(OBJDIR)\*.obj -del /q $(OBJDIR)\*.lib distclean: clean -del /q $(DEBUG_OBJDIR)\*.obj -del /q $(DEBUG_OBJDIR)\*.lib -del /q $(PLAIN_OBJDIR)\*.obj -del /q $(PLAIN_OBJDIR)\*.lib -del /q $(EXEDIR)\*.exe -del /q $(EXEDIR)\*.pdb tstools-1.13~git20151030/README000066400000000000000000000014341261471605300155520ustar00rootroot00000000000000This is a set of cross-platform command line tools for working with MPEG data. The emphasis is on relatively simple tools which concentrate on MPEG (H.264 and H.262) data packaged according to H.222 (i.e., TS or PS), with a particular interest in checking for conformance. Transport Stream (TS) is typically used for distribution of cable and satellite data. Program Stream (PS) is typically used to store data on DVDs. The tools are focused on: * Quick reporting of useful data (tsinfo, stream_type) * Giving a quick overview of the entities in the stream (esdots, psdots) * Reporting on TS packets (tsreport) or ES units/frames/fields (esreport) * Simple manipulation of stream data (es2ts, esfilter, esreverse, esmerge, ts2es) * Streaming of data, possibly with introduced errors (tsplay) tstools-1.13~git20151030/ac3.c000066400000000000000000000104261261471605300155050ustar00rootroot00000000000000/* * Support for ATSC Digital Audio Compression Standard, Revision B * (AC3) audio streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Kynesim, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "printing_fns.h" #include "misc_fns.h" #include "ac3_fns.h" static const unsigned int // Table 5.18, frame sizes l_frmsizecod[19][3] = { { 64, 69, 96 }, { 80, 87, 120 }, { 96, 104, 144 }, { 112, 121, 168 }, { 128, 139, 192 }, { 160, 174, 240 }, { 192, 208, 288 }, { 224, 243, 336 }, { 256, 278, 384 }, { 320, 348, 480 }, { 384, 417, 576 }, { 448, 487, 672 }, { 512, 557, 768 }, { 640, 696, 960 }, { 768, 835, 1152 }, { 896, 975, 1344 }, { 1024, 1114, 1536 }, { 1152, 1253, 1728 }, { 1280, 1393, 1920 } }; /* * Read the next AC3 frame. * * Assumes that the input stream is synchronised - i.e., it does not * try to cope if the next two bytes are not '0000 1011 0111 0111' * * - `file` is the file descriptor of the AC3 file to read from * - `frame` is the AC3 frame that is read * * Returns 0 if all goes well, EOF if end-of-file is read, and 1 if something * goes wrong. */ #define SYNCINFO_SIZE 5 int read_next_ac3_frame(int file, audio_frame_p *frame) { int i, err; byte sync_info[SYNCINFO_SIZE]; byte *data = NULL; int fscod; int frmsizecod; int frame_length; offset_t posn = tell_file(file); err = read_bytes(file, SYNCINFO_SIZE, sync_info); if (err == EOF) return EOF; else if (err) { fprint_err("### Error reading syncinfo from AC3 file\n" " (in frame starting at " OFFSET_T_FORMAT ")\n", posn); return 1; } if (sync_info[0] != 0x0b || sync_info[1] != 0x77) { fprint_err("### AC3 frame does not start with 0x0b77" " syncword - lost synchronisation?\n" " Found 0x%02x%02x instead of 0x0b77\n", (unsigned)sync_info[0], (unsigned)sync_info[1]); fprint_err(" (in frame starting at " OFFSET_T_FORMAT ")\n", posn); return 1; } fscod = sync_info[4] >> 6; if (fscod == 3) { // Bad sample rate code fprint_err("### Bad sample rate code in AC3 syncinfo\n" " (in frame starting at " OFFSET_T_FORMAT ")\n", posn); return 1; } frmsizecod = sync_info[4] & 0x3f; if (frmsizecod > 37) { fprint_err("### Bad frame size code %d in AC3 syncinfo\n", frmsizecod); fprint_err(" (in frame starting at " OFFSET_T_FORMAT ")\n", posn); return 1; } frame_length = l_frmsizecod[frmsizecod >> 1][fscod]; if (fscod == 1) frame_length += frmsizecod & 1; frame_length <<= 1; // Convert from 16-bit words to bytes data = malloc(frame_length); if (data == NULL) { print_err("### Unable to extend data buffer for AC3 frame\n"); return 1; } for (i = 0; i < SYNCINFO_SIZE; i++) data[i] = sync_info[i]; err = read_bytes(file, frame_length - SYNCINFO_SIZE, &data[SYNCINFO_SIZE]); if (err) { if (err == EOF) print_err("### Unexpected EOF reading rest of AC3 frame\n"); else print_err("### Error reading rest of AC3 frame\n"); free(data); return 1; } err = build_audio_frame(frame); if (err) { free(data); return 1; } (*frame)->data = data; (*frame)->data_len = frame_length; return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ac3_fns.h000066400000000000000000000032271261471605300163610ustar00rootroot00000000000000/* * Support for ATSC Digital Audio Compression Standard, Revision B * (AC3) audio streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Kynesim Ltd, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _ac3_fns #define _ac3_fns #include "audio_fns.h" /* * Read the next AC3 frame. * * Assumes that the input stream is synchronised - i.e., it does not * try to cope if the next two bytes are not '0000 1011 0111 0111' * * - `file` is the file descriptor of the AC3 file to read from * - `frame` is the AC3 frame that is read * * Returns 0 if all goes well, EOF if end-of-file is read, and 1 if something * goes wrong. */ extern int read_next_ac3_frame(int file, audio_frame_p *frame); #endif // _ac3_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/accessunit.c000066400000000000000000001344001261471605300171770ustar00rootroot00000000000000/* * Utilities for working with access units in H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include "compat.h" #include "printing_fns.h" #include "es_fns.h" #include "ts_fns.h" #include "nalunit_fns.h" #include "accessunit_fns.h" #include "reverse_fns.h" #define DEBUG 0 /* * Build a new access unit datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ static inline int build_access_unit(access_unit_p *acc_unit, uint32_t index) { int err; access_unit_p new = malloc(SIZEOF_ACCESS_UNIT); if (new == NULL) { print_err("### Unable to allocate access unit datastructure\n"); return 1; } err = build_nal_unit_list(&(new->nal_units)); if (err) { free(new); *acc_unit = NULL; return err; } new->index = index; new->started_primary_picture = FALSE; new->primary_start = NULL; new->ignored_broken_NAL_units = 0; new->frame_num = new->field_pic_flag = new->bottom_field_flag = 0; *acc_unit = new; return 0; } /* * Tidy up an access unit datastructure after we've finished with it. * * If `deep` is TRUE, also frees all of the NAL units in the NAL unit * list (which is normally what we want to do). */ static inline void clear_access_unit(access_unit_p acc_unit, int deep) { free_nal_unit_list(&(acc_unit->nal_units),deep); acc_unit->primary_start = NULL; } /* * Tidy up and free an access unit datastructure after we've finished with it. * * Clears the datastructure, frees it, and returns `acc_unit` as NULL. * * Does nothing if `acc_unit` is already NULL. */ extern void free_access_unit(access_unit_p *acc_unit) { if (*acc_unit == NULL) return; clear_access_unit(*acc_unit,TRUE); free(*acc_unit); *acc_unit = NULL; } /* * Report on this access unit */ extern void report_access_unit(access_unit_p access_unit) { int ii; fprint_msg("Access unit %u",access_unit->index); if (access_unit->started_primary_picture) fprint_msg(" (%s)",access_unit->primary_start->start_reason); print_msg(":\n"); if (access_unit->field_pic_flag) fprint_msg(" %s field of frame %u\n", (access_unit->bottom_field_flag==1?"Bottom":"Top"), access_unit->frame_num); else fprint_msg(" Frame %u\n",access_unit->frame_num); if (access_unit->ignored_broken_NAL_units) fprint_msg(" Ignored %d broken NAL unit%s\n", access_unit->ignored_broken_NAL_units, (access_unit->ignored_broken_NAL_units==1?"":"s")); for (ii=0; iinal_units->length; ii++) { nal_unit_p nal = access_unit->nal_units->array[ii]; if (nal == NULL) print_msg(" \n"); else { fprint_msg(" %c",((access_unit->primary_start == nal)?'*':' ')); report_nal(TRUE,nal); } } } /* * How many slices (VCL NAL units) are there in this access unit? */ static inline int num_slices(access_unit_p access_unit) { int count = 0; int ii; for (ii=0; iinal_units->length; ii++) { if (nal_is_slice(access_unit->nal_units->array[ii])) count ++; } return count; } /* * Retrieve the bounds of this access unit in the file it was read from. * * - `access_unit` is the access unit we're interested in * - `start` is its start position (i.e., the location at which to start * reading to retrieve all of the data for the access unit, including * the 00 00 01 prefix at the start of the first NAL unit therein) * - `length` is the total length of the NAL units within this access unit * * Returns 0 if all goes well, 1 if the access unit has no content. */ extern int get_access_unit_bounds(access_unit_p access_unit, ES_offset *start, uint32_t *length) { int ii; if (access_unit->primary_start == NULL) { print_err("### Cannot determine bounds of an access unit with no content\n"); return 1; } *start = access_unit->nal_units->array[0]->unit.start_posn; *length = 0; // Maybe we should precalculate, or even cache, the total length... for (ii=0; iinal_units->length; ii++) (*length) += access_unit->nal_units->array[ii]->unit.data_len; return 0; } /* * Are all slices in this access unit I slices? */ extern int all_slices_I(access_unit_p access_unit) { int ii; if (access_unit->primary_start == NULL) return FALSE; if (!nal_is_slice(access_unit->primary_start)) return FALSE; // All I if (access_unit->primary_start->u.slice.slice_type == ALL_SLICES_I) return TRUE; // Only one slice, and it's I if (num_slices(access_unit) == 1 && access_unit->primary_start->u.slice.slice_type == SLICE_I) return TRUE; // Are any of the slices not I? for (ii=0; iinal_units->length; ii++) { nal_unit_p nal_unit = access_unit->nal_units->array[ii]; if (nal_is_slice(nal_unit) && nal_unit->u.slice.slice_type != SLICE_I) return FALSE; } return TRUE; } /* * Are all slices in this access unit P slices? */ extern int all_slices_P(access_unit_p access_unit) { int ii; if (access_unit->primary_start == NULL) return FALSE; if (!nal_is_slice(access_unit->primary_start)) return FALSE; // All P if (access_unit->primary_start->u.slice.slice_type == ALL_SLICES_P) return TRUE; // Only one slice, and it's P if (num_slices(access_unit) == 1 && access_unit->primary_start->u.slice.slice_type == SLICE_P) return TRUE; // Are any of the slices not P? for (ii=0; iinal_units->length; ii++) { nal_unit_p nal_unit = access_unit->nal_units->array[ii]; if (nal_is_slice(nal_unit) && nal_unit->u.slice.slice_type != SLICE_P) return FALSE; } return TRUE; } /* * Are all slices in this access unit I or P slices? */ extern int all_slices_I_or_P(access_unit_p access_unit) { int ii; if (access_unit->primary_start == NULL) return FALSE; if (!nal_is_slice(access_unit->primary_start)) return FALSE; // All P or all I if (access_unit->primary_start->u.slice.slice_type == SLICE_I || access_unit->primary_start->u.slice.slice_type == SLICE_P) return TRUE; // Only one slice, and it's P or I if (num_slices(access_unit) == 1 && (access_unit->primary_start->u.slice.slice_type == ALL_SLICES_I || access_unit->primary_start->u.slice.slice_type == ALL_SLICES_P)) return TRUE; // Are any of the slices not either P or I? for (ii=0; iinal_units->length; ii++) { nal_unit_p nal_unit = access_unit->nal_units->array[ii]; if (nal_is_slice(nal_unit) && (nal_unit->u.slice.slice_type != SLICE_I && nal_unit->u.slice.slice_type != SLICE_P)) return FALSE; } return TRUE; } /* * Are all slices in this access unit B slices? */ extern int all_slices_B(access_unit_p access_unit) { int ii; if (access_unit->primary_start == NULL) return FALSE; if (!nal_is_slice(access_unit->primary_start)) return FALSE; // All B if (access_unit->primary_start->u.slice.slice_type == ALL_SLICES_B) return TRUE; // Only one slice, and it's B if (num_slices(access_unit) == 1 && access_unit->primary_start->u.slice.slice_type == SLICE_B) return TRUE; // Are any of the slices not B? for (ii=0; iinal_units->length; ii++) { nal_unit_p nal_unit = access_unit->nal_units->array[ii]; if (nal_is_slice(nal_unit) && nal_unit->u.slice.slice_type != SLICE_B) return FALSE; } return TRUE; } /* * Append a NAL unit to the list of NAL units for this access unit * * NB: `pending` may be NULL * * Returns 0 if it succeeds, 1 if some error occurs. */ static int access_unit_append(access_unit_p access_unit, nal_unit_p nal, int starts_primary, nal_unit_list_p pending) { int err; if (starts_primary && access_unit->started_primary_picture) { // Our caller should have started a new access unit instead fprint_err("### Already had a start of primary picture in access" " unit %d\n",access_unit->index); return 1; } if (starts_primary) { access_unit->primary_start = nal; access_unit->started_primary_picture = TRUE; access_unit->frame_num = nal->u.slice.frame_num; access_unit->field_pic_flag = nal->u.slice.field_pic_flag; access_unit->bottom_field_flag = nal->u.slice.bottom_field_flag; } if (pending != NULL && pending->length > 0) { int ii; for (ii=0; iilength; ii++) { err = append_to_nal_unit_list(access_unit->nal_units, pending->array[ii]); if (err) { fprint_err("### Error extending access unit %d\n", access_unit->index); return err; } } } if (nal != NULL) { err = append_to_nal_unit_list(access_unit->nal_units,nal); if (err) { fprint_err("### Error extending access unit %d\n", access_unit->index); return err; } } return 0; } /* * Merge the NAL units of the second access unit into the first, and then * free the second access unit. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int merge_access_unit_nals(access_unit_p access_unit1, access_unit_p *access_unit2) { int err, ii; for (ii = 0; ii < (*access_unit2)->nal_units->length; ii++) { err = append_to_nal_unit_list(access_unit1->nal_units, (*access_unit2)->nal_units->array[ii]); if (err) { print_err("### Error merging two access units\n"); return err; } } // Don't forget that we're now "sharing" any ignored NAL units access_unit1->ignored_broken_NAL_units += (*access_unit2)->ignored_broken_NAL_units; // Take care not to free the individual NAL units in our second access // unit, as they are still being used by the first clear_access_unit(*access_unit2,FALSE); free(*access_unit2); *access_unit2 = NULL; // Fake the flags in our remaining access unit to make us "look" like // a frame access_unit1->field_pic_flag = 0; return 0; } /* * Write out an access unit as ES. * * Also writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `access_unit` is the access unit to write out * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `output` is the ES file to write to * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_access_unit_as_ES(access_unit_p access_unit, access_unit_context_p context, FILE *output) { int ii, err; for (ii=0; iinal_units->length; ii++) { err = write_ES_unit(output,&(access_unit->nal_units->array[ii]->unit)); if (err) { print_err("### Error writing NAL unit "); report_nal(FALSE,access_unit->nal_units->array[ii]); return err; } } if (context != NULL && context->end_of_sequence) { err = write_ES_unit(output,&(context->end_of_sequence->unit)); if (err) { print_err("### Error writing end of sequence NAL unit "); report_nal(FALSE,context->end_of_sequence); return err; } free_nal_unit(&context->end_of_sequence); } if (context != NULL && context->end_of_stream) { err = write_ES_unit(output,&(context->end_of_stream->unit)); if (err) { print_err("### Error writing end of stream NAL unit "); report_nal(FALSE,context->end_of_sequence); return err; } free_nal_unit(&context->end_of_stream); } return 0; } /* * Write out the (potential) trailing components of an access unit as TS. * * I.e., writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * * Returns 0 if it succeeds, 1 if some error occurs. */ static int write_access_unit_trailer_as_TS(access_unit_context_p context, TS_writer_p tswriter, uint32_t video_pid) { int err; if (context != NULL && context->end_of_sequence) { nal_unit_p nal = context->end_of_sequence; err = write_ES_as_TS_PES_packet(tswriter,nal->unit.data,nal->unit.data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing end of sequence NAL unit "); report_nal(FALSE,nal); return err; } free_nal_unit(&context->end_of_sequence); } if (context != NULL && context->end_of_stream) { nal_unit_p nal = context->end_of_stream; err = write_ES_as_TS_PES_packet(tswriter,nal->unit.data,nal->unit.data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing end of stream NAL unit "); report_nal(FALSE,nal); return err; } free_nal_unit(&context->end_of_stream); } return 0; } /* * Write out an access unit as TS. * * Also writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `access_unit` is the access unit to write out * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_access_unit_as_TS(access_unit_p access_unit, access_unit_context_p context, TS_writer_p tswriter, uint32_t video_pid) { int ii, err; for (ii=0; iinal_units->length; ii++) { nal_unit_p nal = access_unit->nal_units->array[ii]; err = write_ES_as_TS_PES_packet(tswriter, nal->unit.data,nal->unit.data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing NAL unit "); report_nal(FALSE,nal); return err; } } return write_access_unit_trailer_as_TS(context,tswriter,video_pid); } /* * Write out an access unit as TS, with PTS timing in the first PES packet * (and PCR timing in the first TS of the frame). * * Also writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `access_unit` is the access unit to write out * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * - `got_pts` is TRUE if we have a PTS value, in which case * - `pts` is said PTS value * - `got_dts` is TRUE if we also have DTS, in which case * - `dts` is said DTS value. * * If we are given a DTS (which must, by definition, always go up) we will also * use it as the value for PCR. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_access_unit_as_TS_with_pts_dts(access_unit_p access_unit, access_unit_context_p context, TS_writer_p tswriter, uint32_t video_pid, int got_pts, uint64_t pts, int got_dts, uint64_t dts) { int ii, err; for (ii=0; iinal_units->length; ii++) { nal_unit_p nal = access_unit->nal_units->array[ii]; // Only write the first PES packet out with PTS if (ii == 0) err = write_ES_as_TS_PES_packet_with_pts_dts(tswriter, nal->unit.data, nal->unit.data_len, video_pid, DEFAULT_VIDEO_STREAM_ID, got_pts,pts, got_dts,dts); else err = write_ES_as_TS_PES_packet(tswriter, nal->unit.data,nal->unit.data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing NAL unit "); report_nal(FALSE,nal); return err; } } return write_access_unit_trailer_as_TS(context,tswriter,video_pid); } /* * Write out an access unit as TS, with PCR timing in the first TS of the * frame. * * Also writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `access_unit` is the access unit to write out * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * - `pcr_base` and `pcr_extn` encode the PCR value. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_access_unit_as_TS_with_PCR(access_unit_p access_unit, access_unit_context_p context, TS_writer_p tswriter, uint32_t video_pid, uint64_t pcr_base, uint32_t pcr_extn) { int ii, err; for (ii=0; iinal_units->length; ii++) { nal_unit_p nal = access_unit->nal_units->array[ii]; // Only write the first PES packet out with PCR if (ii == 0) err = write_ES_as_TS_PES_packet_with_pcr(tswriter, nal->unit.data, nal->unit.data_len, video_pid, DEFAULT_VIDEO_STREAM_ID, pcr_base,pcr_extn); else err = write_ES_as_TS_PES_packet(tswriter, nal->unit.data,nal->unit.data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing NAL unit "); report_nal(FALSE,nal); return err; } } return write_access_unit_trailer_as_TS(context,tswriter,video_pid); } /* * End this access unit. * * - `access_unit` is the access unit to end. * - if `show_details` is true, then a summary of its contents is printed * out. * * Actually, with the current code scheme, this only does much if * `show_details` is true. However, it may still be a useful hook * for actual work later on. * * Returns 0 if it succeeds, 1 if some error occurs. */ static inline int end_access_unit(access_unit_context_p context, access_unit_p access_unit, int show_details) { if (show_details) { report_access_unit(access_unit); if (context->pending_nal) { print_msg("... pending: "); report_nal(TRUE,context->pending_nal); } if (context->end_of_sequence) { print_msg("--> EndOfSequence "); report_nal(TRUE,context->end_of_sequence); } if (context->end_of_stream) { print_msg("--> EndOfStream "); report_nal(TRUE,context->end_of_stream); } } return 0; } /* * Build a new access unit context datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_access_unit_context(ES_p es, access_unit_context_p *context) { int err; access_unit_context_p new = malloc(SIZEOF_ACCESS_UNIT_CONTEXT); if (new == NULL) { print_err("### Unable to allocate access unit context datastructure\n"); return 1; } new->pending_nal = NULL; new->end_of_stream = NULL; new->end_of_sequence = NULL; new->access_unit_index = 0; new->reverse_data = NULL; new->no_more_data = FALSE; new->earlier_primary_start = NULL; err = build_nal_unit_context(es,&new->nac); if (err) { print_err("### Error building access unit context datastructure\n"); free(new); return err; } err = build_nal_unit_list(&new->pending_list); if (err) { print_err("### Error building access unit context datastructure\n"); free_nal_unit_context(&new->nac); free(new); return err; } *context = new; return 0; } /* * Free a new access unit context datastructure. * * Clears the datastructure, frees it, and returns `context` as NULL. * * Does not free any `reverse_data` datastructure. * * Does nothing if `context` is already NULL. */ extern void free_access_unit_context(access_unit_context_p *context) { access_unit_context_p cc = *context; if (cc == NULL) return; // We assume no-one else has an interest in the NAL units in // our "pending" list. free_nal_unit_list(&cc->pending_list,TRUE); // And similarly, we should be the only "person" holding on to these free_nal_unit(&cc->earlier_primary_start); // although this is bluff free_nal_unit(&cc->end_of_sequence); free_nal_unit(&cc->end_of_stream); free_nal_unit(&cc->pending_nal); free_nal_unit_context(&cc->nac); cc->reverse_data = NULL; free(*context); *context = NULL; return; } /* * Reset an acccess unit context, so it "forgets" its current information * about what it is reading, etc. */ extern void reset_access_unit_context(access_unit_context_p context) { free_nal_unit(&context->earlier_primary_start); free_nal_unit(&context->end_of_sequence); free_nal_unit(&context->end_of_stream); free_nal_unit(&context->pending_nal); reset_nal_unit_list(context->pending_list,FALSE); // @@@ leak??? context->no_more_data = FALSE; // We have to hope that the "previous" sequence parameter and picture // parameter dictionaries are still applicable, since we don't still // have a record of the ones that would have been in effect at this // point. } /* * Rewind a file being read as access units. * * This is a wrapper for `rewind_nal_unit_context` that also knows to * unset things appropriate to the access unit context. * * If a reverse context is attached to this access unit, it also will * be "rewound" appropriately. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int rewind_access_unit_context(access_unit_context_p context) { // First, forget where we are reset_access_unit_context(context); context->access_unit_index = 0; // no access units read from this file yet // Next, take care of rewinding if (context->reverse_data) { context->reverse_data->last_posn_added = -1; // next entry to be 0 } // And then, do the relocation itself return rewind_nal_unit_context(context->nac); } /* * Remember the required information from the previous access unit's * first VLC NAL unit (i.e., the one that starts its primary picture). * * If we just remembered the (address of the) NAL unit itself, we would * have a problem if/when the access unit containing it was freed, since * that would also free the NAL unit. Luckily, the information we want * to remember is well defined, and does not require us to do anything * other than copy data, so we can reuse the same "internal" NAL unit * without needing to do lots of mallocing around. * * It *should* be obvious, given its intended use, but do not call this * on a NAL unit that has not been decoded - things may fall apart * messily later on... * * (NB: the "pseudo" NAL unit we use to remember the information is * a true NAL unit except for not having any of the data/rbsp arrays * filled in, so it *does* cause the NAL unit id to be incremented, * which has confused me at least once when reading diagnostic output.) * * Returns 0 if it succeeds, 1 if some error occurs. */ static int remember_earlier_primary_start(access_unit_context_p context, nal_unit_p nal) { nal_unit_p tgt = context->earlier_primary_start; if (tgt == NULL) { int err = build_nal_unit(&tgt); if (err) { print_err("### Error building NAL unit for 'earlier primary start'\n"); free(tgt); return err; } context->earlier_primary_start = tgt; } tgt->starts_picture_decided = nal->starts_picture_decided; tgt->starts_picture = nal->starts_picture; tgt->start_reason = nal->start_reason; tgt->decoded = nal->decoded; tgt->nal_ref_idc = nal->nal_ref_idc; tgt->nal_unit_type = nal->nal_unit_type; tgt->u = nal->u; // Lastly, we may not need the following, but they are sufficient to // allow us to read the whole NAL unit back in if we should need to. tgt->unit.start_posn = nal->unit.start_posn; tgt->unit.data_len = nal->unit.data_len; return 0; } /* * Maybe remember an access unit for reversing - either an IDR or one with all * frames I */ static int maybe_remember_access_unit(reverse_data_p reverse_data, access_unit_p access_unit, int verbose) { // Keep it if it is an IDR, or all of its contents are I slices if (access_unit->primary_start != NULL && access_unit->primary_start->nal_ref_idc != 0 && (access_unit->primary_start->nal_unit_type == NAL_IDR || all_slices_I(access_unit))) { ES_offset start_posn = {0,0}; uint32_t num_bytes = 0; int err = get_access_unit_bounds(access_unit,&start_posn,&num_bytes); if (err) { fprint_err("### Error working out position/size of access unit %d" " for reversing\n",access_unit->index); return 1; } err = remember_reverse_h264_data(reverse_data,access_unit->index, start_posn,num_bytes); if (err) { fprint_err("### Error remembering access unit %d for reversing\n", access_unit->index); return 1; } if (verbose) fprint_msg("REMEMBER IDR %5d at " OFFSET_T_FORMAT_08 "/%04d for %5d\n",access_unit->index, start_posn.infile,start_posn.inpacket,num_bytes); } return 0; } /* * Retrieve the next access unit from the given elementary stream. * * - `context` is the context information needed to allow us to find * successive access units. * - `quiet` is true if we should try to be silent about it * - `show_details` is true if we should output more info than normal * - `ret_access_unit` is the next access unit. * * If the access unit was ended because an end of sequence or end of * stream NAL unit was encountered, then said end of sequence/stream * NAL unit will be remembered in the `context`. * * Note that it is possible to get back an *empty* access unit in * certain situations - the most obvious of which is if we get two * ``end of sequence`` NAL units with nothing betwen them. * * Because of this possibility, some care should be taken to allow for * access units that do not contain a primary picture (no VCL NAL unit), * and contain zero NAL units. Also, if one is trying for an accurate * count of access units, such instances should probably be ignored. * * Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if * some error occurs. * * EOF can be returned because the end of file has been reached, or because an * end of stream NAL unit has been encountered. The two may be distinguished * by looking at `context->end_of_stream`, which will be NULL if it was a true * EOF. * * Note that `ret_access_unit` will be NULL if EOF is returned. */ extern int get_next_access_unit(access_unit_context_p context, int quiet, int show_details, access_unit_p *ret_access_unit) { int err; nal_unit_p nal = NULL; access_unit_p access_unit; // Is there anything more to read from the input stream? if (context->no_more_data) { *ret_access_unit = NULL; return EOF; } // Since we're expecting to return a new access unit, // we'd better build it... err = build_access_unit(&access_unit,context->access_unit_index+1); if (err) return err; // Did we have any left over stuff to put at its start? if (context->pending_nal != NULL) { err = access_unit_append(access_unit, context->pending_nal,TRUE,context->pending_list); if (err) goto give_up; context->pending_nal = NULL; reset_nal_unit_list(context->pending_list,FALSE); } for (;;) { err = find_next_NAL_unit(context->nac,FALSE,&nal); if (err == EOF) { context->no_more_data = TRUE; // prevent future reads on this stream break; } else if (err == 2) { // The NAL unit was broken. Should we: // a) ignore it and pretend it never happened (i.e., ``continue``) // b) ignore it and give up on the current access unit (i.e., unset // our current status, and hunt for the start of the next access // unit). // Clearly, option (a) is the easiest to try, so let's see how that // works for now... print_err("!!! Ignoring broken NAL unit\n"); access_unit->ignored_broken_NAL_units ++; continue; } else if (err) { print_err("### Error retrieving next NAL\n"); goto give_up; } if (nal_is_slice(nal)) { if (!access_unit->started_primary_picture) { // We're in a new access unit, but we haven't had a slice // yet, so we can be lazy and assume that this must be the // first slice // (What we're *not* checking is whether the first access // unit in the bitstream starts with an IDR, which might be // a good idea) nal->start_reason = "First slice of new access unit"; err = access_unit_append(access_unit,nal,TRUE,context->pending_list); if (err) goto give_up_free_nal; reset_nal_unit_list(context->pending_list,FALSE); err = remember_earlier_primary_start(context,nal); if (err) goto give_up_free_nal; } else if (nal_is_first_VCL_NAL(nal,context->earlier_primary_start)) { // Regardless of what we determine next, we need to remember that the // NAL started (what may later be the previous) access unit err = remember_earlier_primary_start(context,nal); if (err) goto give_up_free_nal; if (access_unit->started_primary_picture) { // We were already in an access unit with a primary // picture, so this NAL unit must start a new access unit. // Remember it for next time, and return the access unit so far. context->pending_nal = nal; break; // Ready to return the access unit } else { // This access unit was waiting for its primary picture err = access_unit_append(access_unit,nal,TRUE,context->pending_list); if (err) goto give_up_free_nal; reset_nal_unit_list(context->pending_list,FALSE); } } else if (!access_unit->started_primary_picture) { // But this is not a NAL unit that may start a new // access unit. So what should we do? Ignore it? if (!quiet) { print_err("!!! Ignoring VCL NAL that cannot start a picture:\n"); print_err(" "); report_nal(FALSE,nal); print_err("\n"); } free_nal_unit(&nal); } else if (nal_is_redundant(nal)) { // pass // print_msg(" ignoring redundant NAL unit\n"); free_nal_unit(&nal); } else { // We're part of the same access unit, but not special err = access_unit_append(access_unit,nal,FALSE,context->pending_list); if (err) goto give_up_free_nal; reset_nal_unit_list(context->pending_list,FALSE); } } else if (nal->nal_unit_type == NAL_ACCESS_UNIT_DELIM) { // We always start an access unit... if (access_unit->started_primary_picture) { err = append_to_nal_unit_list(context->pending_list,nal); if (err) goto give_up_free_nal; break; // Ready to return the "previous" access unit } else { // The current access unit doesn't yet have any VCL NALs if (context->pending_list->length > 0 || access_unit->nal_units->length > 0) { print_err("!!! Ignoring incomplete access unit:\n"); if (access_unit->nal_units->length > 0) { report_nal_unit_list(FALSE," ",access_unit->nal_units); reset_nal_unit_list(access_unit->nal_units,TRUE); } if (context->pending_list->length > 0) { report_nal_unit_list(FALSE," ",context->pending_list); reset_nal_unit_list(context->pending_list,TRUE); } } err = access_unit_append(access_unit,nal,FALSE,NULL); if (err) goto give_up_free_nal; } } else if (nal->nal_unit_type == NAL_SEI) { // SEI units always precede the primary coded picture // - so they also implicitly end any access unit that has already // started its primary picture if (access_unit->started_primary_picture) { err = append_to_nal_unit_list(context->pending_list,nal); if (err) goto give_up_free_nal; break; // Ready to return the "previous" access unit } else { err = append_to_nal_unit_list(context->pending_list,nal); if (err) goto give_up_free_nal; } } else if (nal->nal_unit_type == NAL_SEQ_PARAM_SET || nal->nal_unit_type == NAL_PIC_PARAM_SET || nal->nal_unit_type == 13 || nal->nal_unit_type == 14 || nal->nal_unit_type == 15 || nal->nal_unit_type == 16 || nal->nal_unit_type == 17 || nal->nal_unit_type == 18) { // These start a new access unit *if* they come after the // last VCL NAL of an access unit. But we can only *tell* // that they are after the last VCL NAL of an access unit // when we start the next access unit (!) - so we need to // hold them in hand until we know that we need them. // (i.e., they'll get added to an access unit just before // the next "more determined" NAL unit we add to an access // unit) err = append_to_nal_unit_list(context->pending_list,nal); if (err) goto give_up_free_nal; } else if (nal->nal_unit_type == NAL_END_OF_SEQ) { if (context->pending_list->length > 0) { print_err("!!! Ignoring items after last VCL NAL and" " before End of Sequence:\n"); report_nal_unit_list(FALSE," ",context->pending_list); reset_nal_unit_list(context->pending_list,TRUE); } // And remember this as the End of Sequence marker context->end_of_sequence = nal; break; } else if (nal->nal_unit_type == NAL_END_OF_STREAM) { if (context->pending_list->length > 0) { print_err("!!! Ignoring items after last VCL NAL and" " before End of Stream:\n"); report_nal_unit_list(FALSE," ",context->pending_list); reset_nal_unit_list(context->pending_list,TRUE); } // And remember this as the End of Stream marker context->end_of_stream = nal; // Which means there's no point in reading more from this stream // (setting no_more_data like this means that *next* time this // function is called, it will return EOF) context->no_more_data = TRUE; break; } else { // It's not a slice, or an access unit delimiter, or an // end of sequence or stream, or a sequence or picture // parameter set, or various other odds and ends, so it // looks like we can ignore it. free_nal_unit(&nal); } } // Check for an immediate "end of file with no data" // - i.e., we read EOF or end of stream, and there was nothing // between the last access unit and such reading if (context->no_more_data && access_unit->nal_units->length == 0) { free_access_unit(&access_unit); *ret_access_unit = NULL; return EOF; } // Otherwise, finish off and return the access unit we have in hand err = end_access_unit(context,access_unit,show_details); if (err) goto give_up; // Remember to count it context->access_unit_index ++; *ret_access_unit = access_unit; return 0; give_up_free_nal: free_nal_unit(&nal); give_up: free_access_unit(&access_unit); return 1; } /* * Retrieve the next non-empty access unit from the given elementary stream. * * - `context` is the context information needed to allow us to find * successive access units. * - `quiet` is true if we should try to be silent about it * - `show_details` is true if we should output more info than normal * - `frame` is an access unit datastructure representing the next * frame. * * If the access unit was ended because an end of sequence or end of * stream NAL unit was encountered, then said end of sequence/stream * NAL unit will be remembered in the `context`. * * Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if * some error occurs. * * EOF can be returned because the end of file has been reached, or because an * end of stream NAL unit has been encountered. The two may be distinguished * by looking at `context->end_of_stream`, which will be NULL if it was a true * EOF. * * Note that `ret_access_unit` will be NULL if EOF is returned. */ static int get_next_non_empty_access_unit(access_unit_context_p context, int quiet, int show_details, access_unit_p *access_unit) { for (;;) { int err = get_next_access_unit(context,quiet,show_details,access_unit); if (err) return err; if ((*access_unit)->primary_start) return 0; } } /* * Try for the next field of a pair, and return a frame formed therefrom * * - `context` is the context information needed to allow us to find * successive access units. * - `quiet` is true if we should try to be silent about it * - `show_details` is true if we should output more info than normal * - if `first_time` is true, then we will try to match a second field * with a third, if the second field has a different temporal reference * than the first. If it is false, we will not (thus stopping us from * trying forever...) * - `picture` starts out at the first field of our (hoped for) pair, and * will end up as the merged result of our two fields. If the input stream * is awry (or we are misaligned with respect to it), this might instead be * replaced by a "proper" frame. * * Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if * some error occurs. */ static int get_next_field_of_pair(access_unit_context_p context, int quiet, int show_details, int first_time, access_unit_p *access_unit) { int err; access_unit_p second; if (show_details || context->nac->show_nal_details) fprint_msg("@@ Looking for second field (%s time)\n", (first_time?"first":"second")); // We assume (hope) the next picture will be our second half err = get_next_non_empty_access_unit(context,quiet,show_details,&second); if (err) { if (err != EOF) print_err("### Trying to read second field\n"); return err; } if (second->field_pic_flag == 0) { if (!quiet) print_err("!!! Field followed by a frame - ignoring the field\n"); free_access_unit(access_unit); *access_unit = second; // and pretend to success } else if ((*access_unit)->frame_num == second->frame_num) { // They appear to be matching fields - make a frame from them if (show_details || context->nac->show_nal_details) print_msg("@@ Merging two field access units\n"); err = merge_access_unit_nals(*access_unit,&second); // (frees `second`) if (err) { free_access_unit(&second); return 1; } if (show_details) report_access_unit(*access_unit); } else if (first_time) { if (!quiet) fprint_err("!!! Field with frame number %d (%x) followed by" " field with frame number %d (%x) - ignoring first field\n", (*access_unit)->frame_num,(*access_unit)->frame_num, second->frame_num,second->frame_num); // Try again free_access_unit(access_unit); *access_unit = second; err = get_next_field_of_pair(context,quiet,show_details,FALSE,access_unit); if (err) return 1; } else { print_err("### Adjacent fields do not share frame numbers" " - unable to match fields up\n"); return 1; } return 0; } /* * Retrieve the next H.264 frame from the given elementary stream. * * The next access unit is retrieved from the input stream (using * get_next_access_unit). * * If that access unit represents a frame, it is returned. * * If it represents a field, then the *following* access unit is retrieved, * and if that is the second field of its frame, it is merged into the first, * and the resultant frame is returned. * * If a field with frame number A is followed by a field with frame number B, * it is assumed that synchronisation has been lost. In this case, the first * field (frame A) will be discarded, and an attempt made to read the second * field of frame B. * * Similarly, if a frame is found instead of the second field, the first * field will be discarded and the frame returned. * * Note that if the context is associated with a reverse context, * then appropriate frames will automatically be remembered therein. * * - `context` is the context information needed to allow us to find * successive access units. * - `quiet` is true if we should try to be silent about it * - `show_details` is true if we should output more info than normal * - `frame` is an access unit datastructure representing the next * frame. * * If the access unit was ended because an end of sequence or end of * stream NAL unit was encountered, then said end of sequence/stream * NAL unit will be remembered in the `context`. * * Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if * some error occurs. * * EOF can be returned because the end of file has been reached, or because an * end of stream NAL unit has been encountered. The two may be distinguished * by looking at `context->end_of_stream`, which will be NULL if it was a true * EOF. * * Note that `ret_access_unit` will be NULL if EOF is returned. */ extern int get_next_h264_frame(access_unit_context_p context, int quiet, int show_details, access_unit_p *frame) { int err; access_unit_p access_unit; *frame = NULL; err = get_next_non_empty_access_unit(context,quiet,show_details, &access_unit); if (err) return err; if (access_unit->field_pic_flag == 1) { // We assume (hope) the next access_unit will be our second half // - let's try to get it, and merge it into our current access unit err = get_next_field_of_pair(context,quiet,show_details,TRUE,&access_unit); if (err) { free_access_unit(&access_unit); return 1; } } if (context->reverse_data) { err = maybe_remember_access_unit(context->reverse_data,access_unit, show_details); if (err) { free_access_unit(&access_unit); return 1; } } *frame = access_unit; return 0; } /* * If this access unit was read from PES, did any of its PES packets contain * a PTS? * * Returns TRUE if so, FALSE if not. */ extern int access_unit_has_PTS(access_unit_p access_unit) { // We need to look at each ES unit (within each NAL unit) of this access unit int ii; for (ii=0; iinal_units->length; ii++) { if (access_unit->nal_units->array[ii]->unit.PES_had_PTS) return TRUE; } return FALSE; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/accessunit_defns.h000066400000000000000000000133761261471605300203730ustar00rootroot00000000000000/* * Datastructures for working with access units in H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _accessunit_defns #define _accessunit_defns #include "nalunit_defns.h" #include "es_defns.h" // Since reverse_data refers to h262 and acces_unit datastructures, and // *they* refer to reverse_data, we need to break the circular referencing // at some point typedef struct access_unit_context *access_unit_context_p; struct reverse_data; // ------------------------------------------------------------ // A single access unit struct access_unit { uint32_t index; // The (notional) index of this unit in the stream // (i.e., from the context's access_unit_index) int started_primary_picture; // True if we have, indeed, done so nal_unit_p primary_start; // First NAL unit of our primary picture // (a pointer into `nal_units`) // A "list" of the NAL units that form us // (the only reason we don't use an ES unit list is that I want to be // able to report on the content of an access unit in terms of the NAL // units that form it - perhaps more important in development than in // prodcution, but still) nal_unit_list_p nal_units; // Did we ignore any "broken" NAL units when we were being built? // (if so, there's a case to say we might be broken too) int ignored_broken_NAL_units; // Information derived from the slices in this access unit // (all slices in an access unit must have the same values for these, // so these could actually just be derived from the `primary_start`, // but it's slightly easier to have them more available, and saves // the need to check if `primary_start` is defined before using them). uint32_t frame_num; byte field_pic_flag; // frame or field? byte bottom_field_flag; // for a field (only), bottom or top? // (After merging two field access units into a single frame, // `field_pic_flag` will be set to 0, to "pretend" that we have a // "proper" frame access unit) }; typedef struct access_unit *access_unit_p; #define SIZEOF_ACCESS_UNIT sizeof(struct access_unit) // ------------------------------------------------------------ // Context for looping over the access units in an elementary stream struct access_unit_context { // --------------------------------------------------------------- // Public information - things it makes sense for users to inspect // --------------------------------------------------------------- nal_unit_context_p nac; // short and nasty "mnemonic" // If we read an EndOfStream or EndOfSequence NAL unit, then we // want to remember as much. We don't really want to put it into // the preceding access unit (for a start, that would mess up // reversing the stream), but we also don't want to forget it, // and in the case of EndOfStream, we don't want to carry on // reading the ES after finding it. The simplest thing is just // to remember them. nal_unit_p end_of_sequence; nal_unit_p end_of_stream; // We count all of the access units as we read them (this is useful // when we are building up reverse_data arrays). If functions // move around in the data stream, we assume that they will // (re)set this to a sensible value. // The index of the first access unit read is 1, and this value is // incremented by each call of `get_next_access_unit` uint32_t access_unit_index; // The index of the last access unit read // If we are collecting reversing information, then we keep a reference // to the reverse data here struct reverse_data * reverse_data; // ------------------------------------------------------------- // Private information - used internally by the software, not to // be relied upon by outsiders // ------------------------------------------------------------- // If we ended the previous access unit because of finding a NAL // unit that provokes a *new* access unit, then we remember it // (after all, we'll want to put it into the new access unit) nal_unit_p pending_nal; // We need to remember the VCL NAL unit that started the previous // primary picture, so that we can compare a later VCL NAL unit // to it, to see if we've got a new primary picture starting // (actually, we'll only remember a "stub" of information for it) nal_unit_p earlier_primary_start; // Some items go "in front of" the next VCL NAL unit, so we need // a list of such nal_unit_list_p pending_list; // If we read an end of stream NAL unit, then next time we try // to read an access unit, we want to know that there is no point. // Similarly, if we read EOF on the input stream. byte no_more_data; }; #define SIZEOF_ACCESS_UNIT_CONTEXT sizeof(struct access_unit_context) #endif // _accessunit_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/accessunit_fns.h000066400000000000000000000271321261471605300200550ustar00rootroot00000000000000/* * Functions for working with access units in H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _accessunit_fns #define _accessunit_fns #include "accessunit_defns.h" /* * Build a new access unit context datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_access_unit_context(ES_p es, access_unit_context_p *context); /* * Free a new access unit context datastructure. * * Clears the datastructure, frees it, and returns `context` as NULL. * * Does not free any `reverse_data` datastructure. * * Does nothing if `context` is already NULL. */ extern void free_access_unit_context(access_unit_context_p *context); /* * Reset an acccess unit context, so it "forgets" its current information * about what it is reading, etc. */ extern void reset_access_unit_context(access_unit_context_p context); /* * Rewind a file being read as access units. * * This is a wrapper for `rewind_nal_unit_context` that also knows to * unset things appropriate to the access unit context. * * If a reverse context is attached to this access unit, it also will * be "rewound" appropriately. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int rewind_access_unit_context(access_unit_context_p context); /* * Tidy up and free an access unit datastructure after we've finished with it. * * Clears the datastructure, frees it, and returns `acc_unit` as NULL. * * Does nothing if `acc_unit` is already NULL. */ extern void free_access_unit(access_unit_p *acc_unit); /* * Report on this access unit */ extern void report_access_unit(access_unit_p access_unit); /* * Retrieve the bounds of this access unit in the file it was read from. * * - `access_unit` is the access unit we're interested in * - `start` is its start position (i.e., the location at which to start * reading to retrieve all of the data for the access unit, including * the 00 00 01 prefix at the start of the first NAL unit therein) * - `length` is the total length of the NAL units within this access unit * * Returns 0 if all goes well, 1 if the access unit has no content. */ extern int get_access_unit_bounds(access_unit_p access_unit, ES_offset *start, uint32_t *length); /* * Are all slices in this access unit I slices? */ extern int all_slices_I(access_unit_p access_unit); /* * Are all slices in this access unit P slices? */ extern int all_slices_P(access_unit_p access_unit); /* * Are all slices in this access unit I or P slices? */ extern int all_slices_I_or_P(access_unit_p access_unit); /* * Are all slices in this access unit B slices? */ extern int all_slices_B(access_unit_p access_unit); /* * Write out an access unit as ES. * * Also writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `access_unit` is the access unit to write out * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `output` is the ES file to write to * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_access_unit_as_ES(access_unit_p access_unit, access_unit_context_p context, FILE *output); /* * Write out an access unit as TS. * * Also writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `access_unit` is the access unit to write out * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_access_unit_as_TS(access_unit_p access_unit, access_unit_context_p context, TS_writer_p tswriter, uint32_t video_pid); /* * Write out an access unit as TS, with PTS timing in the first PES packet * (and PCR timing in the first TS of the frame). * * Also writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `access_unit` is the access unit to write out * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * - `pts` is the PTS time (which is also used as the PCR base). * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_access_unit_as_TS_with_pts_dts(access_unit_p access_unit, access_unit_context_p context, TS_writer_p tswriter, uint32_t video_pid, int got_pts, uint64_t pts, int got_dts, uint64_t dts); /* * Write out an access unit as TS, with PCR timing in the first TS of the * frame. * * Also writes out any end of sequence or end of stream NAL unit found in the * `context` (since they are assumed to have immediately followed this access * unit). * * - `access_unit` is the access unit to write out * - `context` may contain additional things to write (see above), but may * legitimately be NULL if there is no context. * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * - `pcr_base` and `pcr_extn` encode the PCR value. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_access_unit_as_TS_with_PCR(access_unit_p access_unit, access_unit_context_p context, TS_writer_p tswriter, uint32_t video_pid, uint64_t pcr_base, uint32_t pcr_extn); /* * Retrieve the next access unit from the given elementary stream. * * - `context` is the context information needed to allow us to find * successive access units. * - `quiet` is true if we should try to be silent about it * - `show_details` is true if we should output more info than normal * - `ret_access_unit` is the next access unit. * * If the access unit was ended because an end of sequence or end of * stream NAL unit was encountered, then said end of sequence/stream * NAL unit will be remembered in the `context`. * * Note that it is possible to get back an *empty* access unit in * certain situations - the most obvious of which is if we get two * ``end of sequence`` NAL units with nothing betwen them. * * Because of this possibility, some care should be taken to allow for * access units that do not contain a primary picture (no VCL NAL unit), * and contain zero NAL units. Also, if one is trying for an accurate * count of access units, such instances should probably be ignored. * * Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if * some error occurs. * * EOF can be returned because the end of file has been reached, or because an * end of stream NAL unit has been encountered. The two may be distinguished * by looking at `context->end_of_stream`, which will be NULL if it was a true * EOF. * * Note that `ret_access_unit` will be NULL if EOF is returned. */ extern int get_next_access_unit(access_unit_context_p context, int quiet, int show_details, access_unit_p *ret_access_unit); /* * Retrieve the next H.264 frame from the given elementary stream. * * The next access unit is retrieved from the input stream (using * get_next_access_unit). * * If that access unit represents a frame, it is returned. * * If it represents a field, then the *following* access unit is retrieved, * and if that is the second field of its frame, it is merged into the first, * and the resultant frame is returned. * * If a field with frame number A is followed by a field with frame number B, * it is assumed that synchronisation has been lost. In this case, the first * field (frame A) will be discarded, and an attempt made to read the second * field of frame B. * * Similarly, if a frame is found instead of the second field, the first * field will be discarded and the frame returned. * * Note that if the context is associated with a reverse context, * then appropriate frames will automatically be remembered therein. * * - `context` is the context information needed to allow us to find * successive access units. * - `quiet` is true if we should try to be silent about it * - `show_details` is true if we should output more info than normal * - `frame` is an access unit datastructure representing the next * frame. * * If the access unit was ended because an end of sequence or end of * stream NAL unit was encountered, then said end of sequence/stream * NAL unit will be remembered in the `context`. * * Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if * some error occurs. * * EOF can be returned because the end of file has been reached, or because an * end of stream NAL unit has been encountered. The two may be distinguished * by looking at `context->end_of_stream`, which will be NULL if it was a true * EOF. * * Note that `ret_access_unit` will be NULL if EOF is returned. */ extern int get_next_h264_frame(access_unit_context_p context, int quiet, int show_details, access_unit_p *frame); /* * If this access unit was read from PES, did any of its PES packets contain * a PTS? * * Returns TRUE if so, FALSE if not. */ extern int access_unit_has_PTS(access_unit_p access_unit); #endif // _accessunit_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/adts.c000066400000000000000000000110541261471605300157700ustar00rootroot00000000000000/* * Support for ISO/IEC 14496-3:2001(E) AAC ADTS audio streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "printing_fns.h" #include "misc_fns.h" #include "adts_fns.h" #define DEBUG 0 /* * Read the next ADTS frame. * * Assumes that the input stream is synchronised - i.e., it does not * try to cope if the next three bytes are not '1111 1111 1111'. * * - `file` is the file descriptor of the ADTS file to read from * - `frame` is the ADTS frame that is read * - `flags` indicates if we are forcing the recognition of "emphasis" * fields, etc. * * Returns 0 if all goes well, EOF if end-of-file is read, and 1 if something * goes wrong. */ extern int read_next_adts_frame(int file, audio_frame_p *frame, unsigned int flags) { #define JUST_ENOUGH 6 // just enough to hold the bits of the headers we want int err, ii; int id, layer; byte header[JUST_ENOUGH]; byte *data = NULL; int frame_length; int has_emphasis = 0; offset_t posn = tell_file(file); #if DEBUG fprint_msg("Offset: " OFFSET_T_FORMAT "\n",posn); #endif err = read_bytes(file,JUST_ENOUGH,header); if (err == EOF) return EOF; else if (err) { fprint_err("### Error reading header bytes of ADTS frame\n" " (in frame starting at " OFFSET_T_FORMAT ")\n",posn); free(data); return 1; } #if DEBUG print_msg("ADTS frame\n"); print_data(TRUE,"Start",header,JUST_ENOUGH,JUST_ENOUGH); #endif if (header[0] != 0xFF || (header[1] & 0xF0) != 0xF0) { fprint_err("### ADTS frame does not start with '1111 1111 1111'" " syncword - lost synchronisation?\n" " Found 0x%X%X%X instead of 0xFFF\n", (unsigned)(header[0] & 0xF0) >> 4, (header[0] & 0x0F), (unsigned)(header[1] & 0xF0) >> 4); fprint_err(" (in frame starting at " OFFSET_T_FORMAT ")\n",posn); return 1; } id = (header[1] & 0x08) >> 3; #if DEBUG fprint_msg(" ID %d (%s)\n",id,(id==1?"MPEG-2 AAC":"MPEG-4")); #endif layer = (header[1] & 0x06) >> 1; if (layer != 0) fprint_msg(" layer is %d, not 0 (in frame at " OFFSET_T_FORMAT ")\n", layer,posn); // Experience appears to show that emphasis doesn't exist in MPEG-2 AVC. // But it does exist in (ID=1) MPEG-4 streams. // // .. or if forced. has_emphasis = (flags & ADTS_FLAG_NO_EMPHASIS) ? 0 : ((flags & ADTS_FLAG_FORCE_EMPHASIS) || !id); if (!has_emphasis) { frame_length = ((header[3] & 0x03) << 11) | (header[4] << 3) | ((unsigned)(header[5] & 0xE0) >> 5); } else { frame_length = (header[4] << 5) | ((unsigned)(header[5] & 0xF8) >> 3); } #if DEBUG fprint_msg(" length %d\n",frame_length); #endif data = malloc(frame_length); if (data == NULL) { print_err("### Unable to extend data buffer for ADTS frame\n"); free(data); return 1; } for (ii=0; iidata = data; (*frame)->data_len = frame_length; return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/adts_defns.h000066400000000000000000000034361261471605300171610ustar00rootroot00000000000000/* * Support for ISO/IEC 14496-3:2001(E) AAC ADTS audio streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _adts_defns #define _adts_defns #include "audio_defns.h" // AAC ADTS provides audio in frames of constant time // Flags for ``read_next_adts_frame`` // // Specify this flag to indicate that there is no emphasis field in the ADTS // header. Generally, MPEG-2 ADTS audio (ID=0) has no emphasis field and // MPEG-4 (ID=1) has emphasis, but some H.264/AAC streams have MPEG-4 ADTS // with no emphasis and in those cases, you'll need this flag. #define ADTS_FLAG_NO_EMPHASIS (1<<0) // Specify this flag to indicate that there is always an emphasis field, even // if the ID says there isn't one - included for symmetry with NO_EMPHASIS. #define ADTS_FLAG_FORCE_EMPHASIS (1<<1) #endif // _adts_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/adts_fns.h000066400000000000000000000045411261471605300166460ustar00rootroot00000000000000/* * Support for ISO/IEC 14496-3:2001(E) AAC ADTS audio streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _adts_fns #define _adts_fns #include "adts_defns.h" #include "audio_fns.h" /* Specify this flag to indicate that there is no emphasis * field in the ADTS header. Generally, MPEG-2 ADTS audio * (ID=0) has no emphasis field and MPEG-4 (ID=1) has * emphasis, but some H.264/AAC streams have * MPEG-4 ADTS with no emphasis and in those cases, you'll * need this flag. */ #define ADTS_FLAG_NO_EMPHASIS (1<<0) /* Specify this flag to indicate that there is always an * emphasis field, even if the ID says there isn't one - * included for symmetry with NO_EMPHASIS. */ #define ADTS_FLAG_FORCE_EMPHASIS (1<<1) /* * Read the next ADTS frame. * * Assumes that the input stream is synchronised - i.e., it does not * try to cope if the next three bytes are not '1111 1111 1111'. * * - `file` is the file descriptor of the ADTS file to read from * - `frame` is the ADTS frame that is read * - `flags` indicates if we are forcing the recognition of "emphasis" * fields, etc. * * Returns 0 if all goes well, EOF if end-of-file is read, and 1 if something * goes wrong. */ extern int read_next_adts_frame(int file, audio_frame_p *frame, unsigned int flags); #endif // _adts_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/audio.c000066400000000000000000000064031261471605300161400ustar00rootroot00000000000000/* * Generic audio functionality * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "printing_fns.h" #include "audio_fns.h" #include "adts_fns.h" #include "l2audio_fns.h" #include "ac3_fns.h" /* * Build a new generic audio frame datastructure * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_audio_frame(audio_frame_p *frame) { audio_frame_p new = malloc(SIZEOF_AUDIO_FRAME); if (new == NULL) { fprint_err("### Unable to allocate audio frame datastructure\n"); return 1; } new->data = NULL; new->data_len = 0; *frame = new; return 0; } /* * Tidy up and free an audio frame datastructure when we've finished with it * * Empties the datastructure, frees it, and sets `frame` to NULL. * * If `frame` is already NULL, does nothing. */ extern void free_audio_frame(audio_frame_p *frame) { if (*frame == NULL) return; if ((*frame)->data != NULL) { free((*frame)->data); (*frame)->data = NULL; } (*frame)->data_len = 0; free(*frame); *frame = NULL; } /* * Read the next audio frame. * * Assumes that the input stream is synchronised - i.e., it does not * try to cope if (for MPEG2) the next three bytes are not '1111 1111 1111'. * * - `file` is the file descriptor of the audio file to read from * - `audio_type` indicates what type of audio - e.g., AUDIO_ADTS * - `frame` is the audio frame that is read * * Returns 0 if all goes well, EOF if end-of-file is read, and 1 if something * goes wrong. */ extern int read_next_audio_frame(int file, int audio_type, audio_frame_p *frame) { switch (audio_type) { case AUDIO_ADTS_MPEG2: return read_next_adts_frame(file,frame,ADTS_FLAG_NO_EMPHASIS); case AUDIO_ADTS_MPEG4: return read_next_adts_frame(file,frame,ADTS_FLAG_FORCE_EMPHASIS); case AUDIO_ADTS: return read_next_adts_frame(file,frame,0); case AUDIO_L2: return read_next_l2audio_frame(file,frame); case AUDIO_AC3: return read_next_ac3_frame(file, frame); default: fprint_err("### Unrecognised audio type %d - cannot get next audio frame\n", audio_type); return 1; } } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/audio_defns.h000066400000000000000000000042771261471605300173330ustar00rootroot00000000000000/* * Support for generic audio streams * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _audio_defns #define _audio_defns #include "h222_defns.h" // A simple wrapper for a frame of audio data struct audio_frame { byte *data; // The frame data, including the syncword at the start uint32_t data_len; }; typedef struct audio_frame *audio_frame_p; #define SIZEOF_AUDIO_FRAME sizeof(struct audio_frame) // The types of audio we know about // These are convenience names, defined in terms of the H222 values #define AUDIO_UNKNOWN 0 // which is a reserved value #define AUDIO_ADTS ADTS_AUDIO_STREAM_TYPE #define AUDIO_L2 MPEG2_AUDIO_STREAM_TYPE #define AUDIO_AC3 ATSC_DOLBY_AUDIO_STREAM_TYPE #define AUDIO_ADTS_MPEG2 0x100 #define AUDIO_ADTS_MPEG4 0x101 #define AUDIO_STR(x) ((x)==AUDIO_UNKNOWN ?"unknown": \ (x)==AUDIO_ADTS ?"ADTS": \ (x)==AUDIO_ADTS_MPEG2?"ADTS-MPEG2": \ (x)==AUDIO_ADTS_MPEG4?"ADTS-MPEG4": \ (x)==AUDIO_L2 ?"MPEG2": \ (x)==AUDIO_AC3 ?"ATSC-AC3": \ "???") #endif // _audio_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/audio_fns.h000066400000000000000000000042721261471605300170150ustar00rootroot00000000000000/* * Generic support for audio streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _audio_fns #define _audio_fns #include "audio_defns.h" /* * Build a new generic audio frame datastructure * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_audio_frame(audio_frame_p *frame); /* * Tidy up and free an audio frame datastructure when we've finished with it * * Empties the datastructure, frees it, and sets `frame` to NULL. * * If `frame` is already NULL, does nothing. */ extern void free_audio_frame(audio_frame_p *frame); /* * Read the next audio frame. * * Assumes that the input stream is synchronised - i.e., it does not * try to cope if (for MPEG2) the next three bytes are not '1111 1111 1111'. * * - `file` is the file descriptor of the audio file to read from * - `audio_type` indicates what type of audio - e.g., AUDIO_ADTS * - `frame` is the audio frame that is read * * Returns 0 if all goes well, EOF if end-of-file is read, and 1 if something * goes wrong. */ extern int read_next_audio_frame(int file, int audio_type, audio_frame_p *frame); #endif // _audio_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/avs.c000066400000000000000000000525661261471605300156430ustar00rootroot00000000000000/* * Datastructures and prototypes for reading AVS elementary streams. * * XXX Ignores the issue of the equivalent of AFD data. This *will* cause * XXX problems if rewinding or filtering is to be done. However, what * XXX needs to be done to fix this can probably be based on the code in * XXX h262.c. * XXX And, also, reversing is not yet supported for AVS, anyway. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "printing_fns.h" #include "avs_fns.h" #include "es_fns.h" #include "ts_fns.h" #include "reverse_fns.h" #include "misc_fns.h" #define DEBUG 0 #define DEBUG_GET_NEXT_PICTURE 0 /* * Return a string representing the start code */ extern const char *avs_start_code_str(byte start_code) { if (start_code < 0xB0) return "Slice"; switch (start_code) { // AVS start codes that we are interested in case 0xB0: return "Video sequence start"; case 0xB1: return "Video sequence end"; case 0xB2: return "User data"; case 0xB3: return "I frame"; case 0xB4: return "Reserved"; case 0xB5: return "Extension start"; case 0xB6: return "P/B frame"; case 0xB7: return "Video edit"; default: return "Reserved"; } } // ------------------------------------------------------------ // AVS item *data* stuff // ------------------------------------------------------------ /* * Build a new AVS frame reading context. * * This acts as a "jacket" around the ES context, and is used when reading * AVS frames with get_next_avs_frame(). It "remembers" the last * item read, which is the first item that was not part of the frame. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_avs_context(ES_p es, avs_context_p *context) { avs_context_p new = malloc(SIZEOF_AVS_CONTEXT); if (new == NULL) { print_err("### Unable to allocate AVS context datastructure\n"); return 1; } new->es = es; new->frame_index = 0; new->last_item = NULL; new->reverse_data = NULL; new->count_since_seq_hdr = 0; *context = new; return 0; } /* * Free an AVS frame reading context. * * Clears the datastructure, frees it, and returns `context` as NULL. * * Does not free any `reverse_data` datastructure. * * Does nothing if `context` is already NULL. */ extern void free_avs_context(avs_context_p *context) { avs_context_p cc = *context; if (cc == NULL) return; if (cc->last_item != NULL) free_ES_unit(&cc->last_item); cc->reverse_data = NULL; free(*context); *context = NULL; return; } /* * Rewind a file being read as AVS frames * * This is a wrapper for `seek_ES` that also knows to unset things appropriate * to the AVS frame reading context. * * If a reverse context is attached to this context, it also will * be "rewound" appropriately. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int rewind_avs_context(avs_context_p context) { ES_offset start_of_file = {0,0}; // First, forget where we are if (context->last_item) { free_ES_unit(&context->last_item); context->last_item = NULL; } context->frame_index = 0; // no frames read from this file yet // Next, take care of rewinding if (context->reverse_data) { context->reverse_data->last_posn_added = -1; // next entry to be 0 context->count_since_seq_hdr = 0; // what else can we do? } // And then, do the relocation itself return seek_ES(context->es,start_of_file); } // ------------------------------------------------------------ // AVS "frames" // ------------------------------------------------------------ /* * Add (the information from) an AVS ES unit to the given frame. * * Note that since this takes a copy of the ES unit data, * it is safe to free the original ES unit. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int append_to_avs_frame(avs_frame_p frame, ES_unit_p unit) { return append_to_ES_unit_list(frame->list,unit); } /* * Determine the picture coding type of an AVS ES unit * * P/B frames are distinguished by their picture coding types. For I frames, * we make one up... * * Returns an appropriate value (0 if none suitable) */ extern int avs_picture_coding_type(ES_unit_p unit) { if (unit->start_code == 0xB3) return AVS_I_PICTURE_CODING; // strictly our invention else if (unit->start_code == 0xB6) { byte picture_coding_type = (unit->data[6] & 0xC0) >> 6; if (picture_coding_type == AVS_P_PICTURE_CODING || picture_coding_type == AVS_B_PICTURE_CODING) return picture_coding_type; else { fprint_err("AVS Picture coding type %d (in %02x)\n", picture_coding_type,unit->data[3]); return 0; } } else { fprint_err("AVS 'frame' with start code %02x does not have picture coding type\n", unit->data[0]); return 0; } } /* * Build a new AVS "frame", starting with the given item (which is * copied, so may be freed after this call). * * Returns 0 if it succeeds, 1 if some error occurs. */ static int build_avs_frame(avs_context_p context, avs_frame_p *frame, ES_unit_p unit) { int err; byte *data = unit->data; avs_frame_p new = malloc(SIZEOF_AVS_FRAME); if (new == NULL) { print_err("### Unable to allocate AVS frame datastructure\n"); return 1; } err = build_ES_unit_list(&(new->list)); if (err) { print_err("### Unable to allocate internal list for AVS frame\n"); free(new); return 1; } // Deduce what we can from the first unit of the "frame" new->start_code = unit->start_code; if (is_avs_frame_item(unit)) { new->picture_coding_type = avs_picture_coding_type(unit); new->is_frame = TRUE; new->is_sequence_header = FALSE; if (new->picture_coding_type != AVS_I_PICTURE_CODING) new->picture_distance = (data[6] << 2) | ((data[7] & 0xC0) >> 6); else // I frames *do* have a picture_distance field, but I'm not interested // and it takes more work to find it... new->picture_distance = 0; } else if (is_avs_seq_header_item(unit)) { new->is_frame = FALSE; new->is_sequence_header = TRUE; new->picture_coding_type = 0xFF; // Meaningless value, just in case new->aspect_ratio = (data[10] & 0x3C) >> 2; new->frame_rate_code = ((data[10] & 0x03) << 2) | ((data[11] & 0xC0) >> 4); #if DEBUG fprint_msg("aspect_ratio=%d, frame_rate_code=%d (%.2f)\n",new->aspect_ratio, new->frame_rate_code,avs_frame_rate(new->frame_rate_code)); #endif } else if (is_avs_seq_end_item(unit)) { new->is_frame = FALSE; new->is_sequence_header = FALSE; new->picture_coding_type = 0xFF; // Meaningless value, just in case } else { fprint_err("!!! Building AVS frame that starts with a %s (%02x)\n", avs_start_code_str(unit->start_code),unit->start_code); new->is_frame = FALSE; new->is_sequence_header = FALSE; new->picture_coding_type = 0xFF; // Meaningless value, just in case } err = append_to_avs_frame(new,unit); if (err) { fprint_err("### Error appending first ES unit to AVS %s\n", avs_start_code_str(unit->start_code)); free_avs_frame(&new); return 1; } *frame = new; return 0; } /* * Free an AVS "frame". * * Clears the datastructure, frees it, and returns `frame` as NULL. * * Does nothing if `frame` is already NULL. */ extern void free_avs_frame(avs_frame_p *frame) { avs_frame_p pic = *frame; if (pic == NULL) return; if (pic->list != NULL) free_ES_unit_list(&pic->list); free(*frame); *frame = NULL; return; } #if DEBUG_GET_NEXT_PICTURE /* * Print a representation of an item for debugging */ static void _show_item(ES_unit_p unit) { print_msg("__ "); if (unit == NULL) { print_msg("\n"); return; } if (is_avs_frame_item(unit)) fprint_msg("%s frame",AVS_PICTURE_CODING_STR(avs_picture_coding_type(unit))); else fprint_msg("%s",avs_start_code_str(unit->start_code)); fprint_msg(" at " OFFSET_T_FORMAT "/%d for %d\n", unit->start_posn.infile,unit->start_posn.inpacket,unit->data_len); } #endif /* * Retrieve the the next AVS "frame". * * The AVS "frame" returned can be one of: * * 1. A frame, including its data. * 2. A sequence header, including its sequence extension, if any. * 3. A sequence end. * * - `context` is the AVS frame reading context. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `frame` is the AVS "frame", containing a frame, a sequence header or a * sequence end * * Returns 0 if it succeeds, EOF if we reach the end of file, or 1 if some * error occurs. */ static int get_next_avs_single_frame(avs_context_p context, int verbose, avs_frame_p *frame) { int err = 0; int in_sequence_header = FALSE; int in_sequence_end = FALSE; int in_frame = FALSE; int last_was_slice = FALSE; ES_unit_p item = context->last_item; #if DEBUG_GET_NEXT_PICTURE int num_slices = 0; int had_slice = FALSE; int last_slice_start_code = 0; if (verbose && context->last_item) print_msg("__ reuse last item\n"); #endif context->last_item = NULL; // Find the first item of our next "frame" for (;;) { if (item == NULL) { err = find_and_build_next_ES_unit(context->es,&item); if (err) return err; } if (is_avs_frame_item(item)) { in_frame = TRUE; break; } else if (is_avs_seq_header_item(item)) { in_sequence_header = TRUE; break; } else if (is_avs_seq_end_item(item)) { in_sequence_end = TRUE; break; } #if DEBUG_GET_NEXT_PICTURE else if (verbose) _show_item(item); #endif free_ES_unit(&item); } #if DEBUG_GET_NEXT_PICTURE if (verbose) { print_msg("__ --------------------------------- \n"); _show_item(item); } #endif err = build_avs_frame(context,frame,item); if (err) return 1; free_ES_unit(&item); if (in_sequence_end) { // A sequence end is a single item, so we're done #if DEBUG_GET_NEXT_PICTURE if (verbose) print_msg("__ --------------------------------- \n"); #endif return 0; } // Now find all the rest of the frame/sequence header for (;;) { err = find_and_build_next_ES_unit(context->es,&item); if (err) { if (err != EOF) free_avs_frame(frame); return err; } if (in_frame) { // Have we just finished a frame? // We know we have if the last item was a slice, but this one isn't if (last_was_slice && !is_avs_slice_item(item)) break; last_was_slice = is_avs_slice_item(item); } else if (in_sequence_header) { // Have we just finished a sequence header and its friends? // We know we have if we've hit something that isn't an // extension start or user data start code (perhaps we could // get away with just keeping the (in MPEG-2) sequence_extension, // but it's safer (and simpler) to keep the lot if (!is_avs_extension_start_item(item) && !is_avs_user_data_item(item)) break; } #if DEBUG_GET_NEXT_PICTURE if (verbose) { if (!had_slice) _show_item(item); if (is_avs_slice_item(item)) { num_slices ++; last_slice_start_code = item->start_code; if (!had_slice) had_slice = TRUE; } } #endif // Don't forget to remember the actual item err = append_to_avs_frame(*frame,item); if (err) { print_err("### Error adding item to AVS sequence header\n"); free_avs_frame(frame); return 1; } free_ES_unit(&item); } if (in_frame) context->frame_index ++; context->last_item = item; #if DEBUG_GET_NEXT_PICTURE if (verbose) { if (in_frame) { if (num_slices > 1) { ES_unit_p unit = &(*frame)->list->array[(*frame)->list->length-1]; print_msg("__ ...\n"); fprint_msg("__ slice %2x",last_slice_start_code); fprint_msg(" at " OFFSET_T_FORMAT "/%d for %d\n", unit->start_posn.infile,unit->start_posn.inpacket, unit->data_len); } fprint_msg("__ (%2d slices)\n",num_slices); } print_msg("__ --------------------------------- \n"); if (in_frame || in_sequence_header) _show_item(item); } #endif return 0; } /* * Retrieve the the next AVS "frame". * * The AVS "frame" returned can be one of: * * 1. A frame, including its slices. * 2. A sequence header, including its sequence extension, if any. * 3. A sequence end. * * Specifically, the next AVS "frame" is retrieved from the input stream. * * If that "frame" represents a sequence header or a frame, it is returned. * * Note that if the context is associated with a reverse context, * then appropriate frames/sequence headers will automatically be * remembered therein. * * - `context` is the AVS frame reading context. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `frame` is the AVS "frame", containing a frame, a sequence header or a * sequence end * * Returns 0 if it succeeds, EOF if we reach the end of file, or 1 if some * error occurs. */ extern int get_next_avs_frame(avs_context_p context, int verbose, int quiet, avs_frame_p *frame) { int err; err = get_next_avs_single_frame(context,verbose,frame); if (err) return err; #if 0 if (context->reverse_data) { err = maybe_remember_this_frame(context,verbose,*frame); if (err) { free_avs_frame(frame); return 1; } } #endif return 0; } /* * Write out an AVS frame as TS * * - `tswriter` is TS the output stream * - `frame` is the frame to write out * - `pid` is the PID to use for the TS packets * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_avs_frame_as_TS(TS_writer_p tswriter, avs_frame_p frame, uint32_t pid) { int ii; ES_unit_list_p list; if (frame == NULL || frame->list == NULL) return 0; list = frame->list; for (ii = 0; ii < list->length; ii++) { int err; ES_unit_p unit = &(list->array[ii]); err = write_ES_as_TS_PES_packet(tswriter,unit->data,unit->data_len,pid, DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing out frame list to TS\n"); return err; } } return 0; } /* * Write out an AVS frame as TS, with PTS timing in the first PES packet * (and PCR timing in the first TS of the frame). * * - `frame` is the frame to write out * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * - `got_pts` is TRUE if we have a PTS value, in which case * - `pts` is said PTS value * - `got_dts` is TRUE if we also have DTS, in which case * - `dts` is said DTS value. * * If we are given a DTS (which must, by definition, always go up) we will also * use it as the value for PCR. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_avs_frame_as_TS_with_pts_dts(avs_frame_p frame, TS_writer_p tswriter, uint32_t video_pid, int got_pts, uint64_t pts, int got_dts, uint64_t dts) { int ii; ES_unit_list_p list; if (frame == NULL || frame->list == NULL) return 0; list = frame->list; for (ii = 0; ii < list->length; ii++) { int err; ES_unit_p unit = &(list->array[ii]); // Only write the first PES packet out with PTS if (ii == 0) err = write_ES_as_TS_PES_packet_with_pts_dts(tswriter, unit->data, unit->data_len, video_pid, DEFAULT_VIDEO_STREAM_ID, got_pts,pts, got_dts,dts); else err = write_ES_as_TS_PES_packet(tswriter, unit->data,unit->data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing out frame list to TS\n"); return err; } } return 0; } /* * Write out an AVS frame as TS, with PCR timing in the first TS of the * frame. * * - `frame` is the frame to write out * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * - `pcr_base` and `pcr_extn` encode the PCR value. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_avs_frame_as_TS_with_PCR(avs_frame_p frame, TS_writer_p tswriter, uint32_t video_pid, uint64_t pcr_base, uint32_t pcr_extn) { int ii; ES_unit_list_p list; if (frame == NULL || frame->list == NULL) return 0; list = frame->list; for (ii = 0; ii < list->length; ii++) { int err; ES_unit_p unit = &(list->array[ii]); // Only write the first PES packet out with PCR if (ii == 0) err = write_ES_as_TS_PES_packet_with_pcr(tswriter, unit->data, unit->data_len, video_pid, DEFAULT_VIDEO_STREAM_ID, pcr_base,pcr_extn); else err = write_ES_as_TS_PES_packet(tswriter, unit->data,unit->data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing out frame list to TS\n"); return err; } } return 0; } /* * Write out a frame (as stored in an ES unit list) as ES * * - `output` is the ES output file * - `frame` is the frame to write out * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_avs_frame_as_ES(FILE *output, avs_frame_p frame) { int ii; ES_unit_list_p list; if (frame == NULL || frame->list == NULL) return 0; list = frame->list; for (ii = 0; ii < list->length; ii++) { int err; ES_unit_p unit = &(list->array[ii]); err = write_ES_unit(output,unit); if (err) { print_err("### Error writing out frame list to ES\n"); return err; } } return 0; } /* * Report on an AVS frame's contents. * * - `stream` is where to write the information * - `frame` is the frame to report on * - if `report_data`, then the component ES units will be printed out as well */ extern void report_avs_frame(avs_frame_p frame, int report_data) { if (frame->is_frame) { fprint_msg("%s #%02d", AVS_PICTURE_CODING_STR(frame->picture_coding_type), frame->picture_distance); print_msg("\n"); } else if (frame->is_sequence_header) { print_msg("Sequence header: "); fprint_msg(" frame rate %d (%.2f), aspect ratio %d (%s)", frame->frame_rate_code, avs_frame_rate(frame->frame_rate_code), frame->aspect_ratio, (frame->aspect_ratio==1?"SAR: 1.0": frame->aspect_ratio==2?"4/3": frame->aspect_ratio==3?"16/9": frame->aspect_ratio==4?"2.21/1":"???")); print_msg("\n"); } else { print_msg("Sequence end\n"); } if (report_data) report_ES_unit_list("ES units",frame->list); } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/avs_defns.h000066400000000000000000000124651261471605300170210ustar00rootroot00000000000000/* * Datastructures for reading AVS elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _avs_defns #define _avs_defns #include #include "compat.h" #include "es_defns.h" #include "ts_defns.h" // Since reverse_data refers to avs and acces_unit datastructures, and // *they* refer to reverse_data, we need to break the circular referencing // at some point typedef struct avs_context *avs_context_p; #include "reverse_defns.h" // ------------------------------------------------------------ // An AVS frame. This might be a picture or a sequence header (and // its associated ES units). struct _avs_frame { // The main thing we need is a list of the units that make up this picture ES_unit_list_p list; // An AVS "picture" might be a "proper" picture, a sequence header, // or (just) a sequence end item. It's useful to be able to identify // the two more common cases easily int is_frame; int is_sequence_header; // It's also useful to remember what the first ES unit is int start_code; // Data defined for a frame. byte picture_coding_type; // I, P or B (0, 1, 2) byte picture_distance; // presentation order of P/B frames // Data defined for a sequence header byte aspect_ratio; // 1=SAR/1.0 2=4/3, 3=16/9, 4=2.21/1 (?) byte frame_rate_code; // see Table 7-6 }; typedef struct _avs_frame *avs_frame_p; #define SIZEOF_AVS_FRAME sizeof(struct _avs_frame) // ------------------------------------------------------------ #define is_avs_slice_item(unit) ((unit)->start_code<0xB0) #define is_avs_frame_item(unit) ((unit)->start_code==0xB3 || \ (unit)->start_code==0xB6) #define is_avs_user_data_item(unit) ((unit)->start_code==0xB2) #define is_avs_seq_header_item(unit) ((unit)->start_code==0xB0) #define is_avs_seq_end_item(unit) ((unit)->start_code==0xB1) #define is_avs_extension_start_item(unit) ((unit)->start_code==0xB5) #define is_avs_video_edit_item(unit) ((unit)->start_code==0xB7) #define avs_frame_rate(code) ((code)==1?24000.0/1001: /* 23.967... */\ (code)==2?24: \ (code)==3?25: \ (code)==4?30000.0/1001: /* 29.97... */ \ (code)==5?30: \ (code)==6?50: \ (code)==7?60000.0/1001: /* 59.94... */ \ (code)==8?60: \ 25) /* Hmm-really an error */ #define AVS_I_PICTURE_CODING 0 // our invention, but reasonable in context #define AVS_P_PICTURE_CODING 1 #define AVS_B_PICTURE_CODING 2 // Note that "I" is made up by us (there is no picture coding on I frames) #define AVS_PICTURE_CODING_STR(s) \ ((s)==AVS_I_PICTURE_CODING?"I": \ (s)==AVS_P_PICTURE_CODING?"P": \ (s)==AVS_B_PICTURE_CODING?"B": \ "Reserved") // ------------------------------------------------------------ // Context for looping over the AVS items and pictures in an elementary // stream struct avs_context { ES_p es; // We count all of the frames as we read them (this is useful // when we are building up reverse_data arrays). If functions // move around in the data stream, we assume that they will // (re)set this to a sensible value. // The index of the first frame read is 1, and this value is // incremented by each call of `get_next_avs_frame` (note that // for this purpose, sequence headers are *not* considered frames) uint32_t frame_index; // The index of the last frame read // We detect the end of an AVS frame (or sequence header) by // reading the first item that cannot be part of it. We then need // to remember that item for *next* time we try to read a frame. ES_unit_p last_item; // If we are collecting reversing information, then we keep a reference // to the reverse data here reverse_data_p reverse_data; // In the same context, we need to remember how long it is since the // last sequence header byte count_since_seq_hdr; }; #define SIZEOF_AVS_CONTEXT sizeof(struct avs_context) #endif // _avs_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/avs_fns.h000066400000000000000000000151401261471605300165010ustar00rootroot00000000000000/* * Prototypes for reading AVS elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _avs_fns #define _avs_fns #include "avs_defns.h" /* * Return a string representing the start code */ extern const char *avs_start_code_str(byte start_code); /* * Determine the picture coding type of an AVS ES unit * * P/B frames are distinguished by their picture coding types. For I frames, * we make one up... * * Returns an appropriate value (0 if none suitable) */ extern int avs_picture_coding_type(ES_unit_p unit); /* * Build a new AVS frame reading context. * * This acts as a "jacket" around the ES context, and is used when reading * AVS frames with get_next_avs_frame(). It "remembers" the last * item read, which is the first item that was not part of the frame. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_avs_context(ES_p es, avs_context_p *context); /* * Free an AVS frame reading context. * * Clears the datastructure, frees it, and returns `context` as NULL. * * Does not free any `reverse_data` datastructure. * * Does nothing if `context` is already NULL. */ extern void free_avs_context(avs_context_p *context); /* * Rewind a file being read as AVS frames * * This is a wrapper for `seek_ES` that also knows to unset things appropriate * to the AVS frame reading context. * * If a reverse context is attached to this context, it also will * be "rewound" appropriately. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int rewind_avs_context(avs_context_p context); /* * Free an AVS "frame". * * Clears the datastructure, frees it, and returns `frame` as NULL. * * Does nothing if `frame` is already NULL. */ extern void free_avs_frame(avs_frame_p *frame); /* * Retrieve the the next AVS "frame". * * The AVS "frame" returned can be one of: * * 1. A frame, including its slices. * 2. A sequence header, including its sequence extension, if any. * 3. A sequence end. * * Specifically, the next AVS "frame" is retrieved from the input stream. * * If that "frame" represents a sequence header or a frame, it is returned. * * Note that if the context is associated with a reverse context, * then appropriate frames/sequence headers will automatically be * remembered therein. * * - `context` is the AVS frame reading context. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `frame` is the AVS "frame", containing a frame, a sequence header or a * sequence end * * Returns 0 if it succeeds, EOF if we reach the end of file, or 1 if some * error occurs. */ extern int get_next_avs_frame(avs_context_p context, int verbose, int quiet, avs_frame_p *frame); /* * Write out an AVS frame as TS * * - `tswriter` is TS the output stream * - `frame` is the frame to write out * - `pid` is the PID to use for the TS packets * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_avs_frame_as_TS(TS_writer_p tswriter, avs_frame_p frame, uint32_t pid); /* * Write out an AVS frame as TS, with PTS timing in the first PES packet * (and PCR timing in the first TS of the frame). * * - `frame` is the frame to write out * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * - `got_pts` is TRUE if we have a PTS value, in which case * - `pts` is said PTS value * - `got_dts` is TRUE if we also have DTS, in which case * - `dts` is said DTS value. * * If we are given a DTS (which must, by definition, always go up) we will also * use it as the value for PCR. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_avs_frame_as_TS_with_pts_dts(avs_frame_p frame, TS_writer_p tswriter, uint32_t video_pid, int got_pts, uint64_t pts, int got_dts, uint64_t dts); /* * Write out an AVS frame as TS, with PCR timing in the first TS of the * frame. * * - `frame` is the frame to write out * - `tswriter` is the TS context to write with * - `video_pid` is the PID to use to write the data * - `pcr_base` and `pcr_extn` encode the PCR value. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_avs_frame_as_TS_with_PCR(avs_frame_p frame, TS_writer_p tswriter, uint32_t video_pid, uint64_t pcr_base, uint32_t pcr_extn); /* * Write out a frame (as stored in an ES unit list) as ES * * - `output` is the ES output file * - `frame` is the frame to write out * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_avs_frame_as_ES(FILE *output, avs_frame_p frame); /* * Report on an AVS frame's contents. * * - `frame` is the frame to report on * - if `report_data`, then the component ES units will be printed out as well */ extern void report_avs_frame(avs_frame_p frame, int report_data); #endif // _avs_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/bitdata.c000066400000000000000000000136331261471605300164520ustar00rootroot00000000000000/* * Functions to handle byte data as bit data, and particularly to read * Exp-Golomb encoded data. * * See H.264 clause 10. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "bitdata_fns.h" #include "printing_fns.h" static int MASK[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; /* * Build a new bitdata datastructure. * * - `data` is the byte array we're extracting bits from. * - `data_len` is its length (in bytes). * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_bitdata(bitdata_p *bitdata, byte data[], int data_len) { bitdata_p new = malloc(SIZEOF_BITDATA); if (new == NULL) { print_err("### Unable to allocate bitdata datastructure\n"); return 1; } new->data = data; new->data_len = data_len; new->cur_byte = 0; new->cur_bit = -1; *bitdata = new; return 0; } /* * Tidy up and free a bitdata datastructure after we've finished with it. * * Clears the bitdata datastructure, frees it, and sets `bitdata` to NULL. * * Does nothing if `bitdata` is already NULL. */ extern void free_bitdata(bitdata_p *bitdata) { if (*bitdata == NULL) return; (*bitdata)->data = NULL; (*bitdata)->cur_byte = 0; (*bitdata)->cur_bit = -1; free(*bitdata); *bitdata = NULL; } /* * Return the next bit from the data. * * Returns 0 or 1 if it reads the bit correctly, -1 if there are no more * bits to be read. */ static inline int next_bit(bitdata_p bitdata) { bitdata->cur_bit += 1; if (bitdata->cur_bit == 8) { bitdata->cur_bit = 0; bitdata->cur_byte += 1; if (bitdata->cur_byte > (bitdata->data_len - 1)) { print_err("### No more bits to read from input stream\n"); return -1; } } return (bitdata->data[bitdata->cur_byte] & MASK[bitdata->cur_bit]) >> (7 - bitdata->cur_bit); } /* * Return the next bit from the data. * * Returns 0 if it reads the bit correctly, 1 if there are no more * bits to be read. */ extern int read_bit(bitdata_p bitdata, byte *bit) { int next = next_bit(bitdata); if (next < 0) return 1; else { *bit = next; return 0; } } /* * Reads `count` bits from the data. * * Note it is asserted that `count` must be in the range 0..32. * * Returns 0 if all went well, 1 if there were not enough bits in the data. */ extern int read_bits(bitdata_p bitdata, int count, uint32_t *bits) { int index = 0; uint32_t result = 0; assert((count >=0 && count <= 32)); for (index=0; index=0 && count <= 8)); for (index=0; index // Windows doesn't seem to supply , so we shall have to try // for values we hope work typedef __int8 int8_t; typedef __int16 int16_t; typedef __int32 int32_t; typedef __int64 int64_t; typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; typedef unsigned __int64 uint64_t; typedef uint8_t byte; #define INT64_MIN (-9223372036854775807i64 - 1) // On BSD, lseek takes a 64-bit off_t value // On Linux, if the system supports long files, it does the same // On Windows, one has the choice of _lseek or _lseeki64 #define lseek _lseeki64 // MS Visual C 2003 for .Net defines off_t in sys/types.h as "long" // I want to use the same name for my file offsets on Windows and Unix, // but I also want to use a 64 bit quantity. So: typedef __int64 offset_t; // On Windows, printf supports %lld but only uses 32 bits of the input value, // which leads to confusing results. Correct representation of 64 bit integers, // requires the use of %I64d, which is suitable for printing out offset_t #define OFFSET_T_FORMAT "%I64d" #define OFFSET_T_FORMAT_8 "%8I64d" #define OFFSET_T_FORMAT_08 "%08I64d" // Whilst we're at it, define the format for a 64 bit integer as such #define LLD_FORMAT "%I64d" #define LLU_FORMAT "%I64u" #define LLD_FORMAT_STUMP "I64d" #define LLU_FORMAT_STUMP "I64u" // The MSDN documentation for Visual Studio seems to indicate that // the low-level "names" for stdin, etc., are not STDIN_FILENO, etc., // but are instead stdin, etc. // This seems to naturally be confusing with the C terms stdin, etc. // It *may* be that they actually are not distinct. However, the *numbers* // follow the normal definitions. #ifndef STDIN_FILENO #define STDIN_FILENO 0 #endif // On Windows, "inline" is a C++ only keyword. In C, it is: #define inline __inline // Miscellaneous other Windows-related issues... #define snprintf _snprintf #else // _WIN32 // Other than on Windows, using the C99 integer definitions is what people // expect, so do so #include #include // Keep "byte" for historical/affectionate reasons typedef uint8_t byte; // lseek on BSD/Linux uses an off_t quantity to specify the required // position. Where 64 bit file positions are supported, this is a 64 bit // value. Unfortunately, Windows has off_t defined as being a long. // For compatibility, therefore, we define a type that can be used on // both Windows and Unix // NB: On some systems, off_t is provided by unistd.h, but on some others // it may also be necessary to explicitly include sys/types.h. We shall // do both, here, for safety. #include #include #include typedef off_t offset_t; #if defined(__linux__) && !defined(__USE_FILE_OFFSET64) // If Linux does not have 64 bit support built in, then our offsets will // be just 32 bit integers #define OFFSET_T_FORMAT "%ld" #define OFFSET_T_FORMAT_08 "%08ld" // deprecated, because it looks like hex/octal #define OFFSET_T_FORMAT_8 "%8ld" #else // On Unices, printf supports %lld for 64 bit integers, and this is suitable // for printing out offset_t when it is 64 bit #define OFFSET_T_FORMAT "%" PRIi64 #define OFFSET_T_FORMAT_08 "%08" PRIi64 // deprecated, because it looks like hex/octal #define OFFSET_T_FORMAT_8 "%8" PRIi64 #endif // Whilst we're at it, define the format for a 64 bit integer as such #define LLD_FORMAT "%" PRId64 #define LLU_FORMAT "%" PRIu64 #define LLD_FORMAT_STUMP "lld" #define LLU_FORMAT_STUMP "llu" // Useful macros, but not side-effect free #define max(i,j) ((i)>(j)?(i):(j)) #define min(i,j) ((i)<(j)?(i):(j)) #endif // WIN32 // Other useful things typedef void * void_p; #define TRUE 1 #define FALSE 0 // The following defaults are common, and it's difficult // to decide which other header file they might belong in #define DEFAULT_VIDEO_PID 0x68 #define DEFAULT_AUDIO_PID 0x67 #define DEFAULT_PMT_PID 0x66 #endif /* _compat */ // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/data/000077500000000000000000000000001261471605300156015ustar00rootroot00000000000000tstools-1.13~git20151030/data/README.txt000066400000000000000000000060671261471605300173100ustar00rootroot00000000000000Test data ========= It can be difficult to find test data, particularly for Transport Stream. It is especially difficult to work out what one is allowed to copy and put on one's own website. The doctests in the "python" directory (sibling to this data directory) need at least *some* predictable data to play with. Contents of this directory ========================== For Linux users, a convenient ``setup.sh`` is provided, which will use ``wget`` to download a segment of `Elephant's Dream`_, ``unzip`` it, and produce an ES file from (the resulting) TS file. This is then used as test data by the doctests in the sibling ``python`` directory. Useful links ============ Consolidated list of test video clip resources ---------------------------------------------- http://forum.doom9.org/archive/index.php/t-135034.html A hopefully usedul resource on the Doom9 forum. Note the link to a list of "broken" streams: http://forum.doom9.org/showthread.php?t=134693 Elephant's Dream ---------------- http://www.elephantsdream.org/ Last checked: 2008-08-29 """Elephants Dream is the world’s first open movie, made entirely with open source graphics software such as Blender, and with all production files freely available to use however you please, under a Creative Commons license.""" It is thus clearly allowable to use this data for testing purposes -- see below. W6RZ Homepage -- MPEG-2 Transport Stream Test Patterns and Tools ---------------------------------------------------------------- http://www.w6rz.net/ Last checked: 2008-08-29 This appears to be a very useful resource. It includes a variety of HD Transport Stream samples, including segments of a TS version of Elephant's Dream. There is a discussion about the site at http://www.avsforum.com/avs-vb/archive/index.php/t-570937.html (posts date from 2005 to 2008-11-08, as I'm typing this). MPEG 1 layer 1/2/3 MPEG 2 2/3 Test Data at mpegedit.org --------------------------------------------------------- http://mpgedit.org/mpgedit/mpgedit/testdata/mpegdata.html Last checked: 2008-08-29 (I've not tried any of the data yet, though)) I found this by following a link from http://www.mpeg.org/MPEG/mpeg-systems-resources-and-software/mpeg-systems-test-bitstreams.html ftp://vqeg.its.bldrdoc.gov/HDTV/SVT_exports/ ------------------------------------------- This site is referenced from the W6RZ site, as the source of the "Park Run" clip (a version of which is on the W6RZ site). Note that the README.txt at this location puts restrictions on the use of the data on this site, specifically: """Individuals and organizations extracting sequences from this archive agree that the sequences and all intellectual property rights therein remain the property of Sveriges Television AB (SVT), Sweden. These sequences may only be used for the purpose of developing, testing and presenting technology standards. SVT makes no warranties with respect to the materials and expressly disclaim any warranties regarding their fitness for any purpose.""" The PDF svt_widexga_final.pdf appears to be an interesting paper, and gives previews of each clip. tstools-1.13~git20151030/data/setup.sh000077500000000000000000000010451261471605300173000ustar00rootroot00000000000000#! /bin/sh # # A very simple script to retrieve and pre-process test data, as used by # ../python/rundoctest.py # # Assumes it is being run in the 'data' directory # Assumes the presence of 'wget' and 'unzip', and that the tstools have been # built. # Retrieve a segment of Elephant's Dream in TS (11 is the smallest segment) wget http://www.w6rz.net/ed24p_11.zip unzip ed24p_11.zip ts2es ed24p_11.ts ed24p_11.video.es # Afterwards: # # ed24p_11.zip 146M # ed26p_11.ts 314M # ed24p_11.video.es 147M tstools-1.13~git20151030/debian/000077500000000000000000000000001261471605300161125ustar00rootroot00000000000000tstools-1.13~git20151030/debian/.gitignore000066400000000000000000000000111261471605300200720ustar00rootroot00000000000000tstools/ tstools-1.13~git20151030/debian/changelog000066400000000000000000000007561261471605300177740ustar00rootroot00000000000000tstools (1.13-1) saucy; urgency=low * Update to current *** Fake bug no for lintian *** (Closes: #905304) -- John Cox Thu, 29 Oct 2015 15:47:39 +0000 tstools (1.11-1ubuntu1) oneiric; urgency=low * Fix FTBFS on Ubuntu 11.10 (LP: #832801). -- Stephen M. Webb Mon, 12 Sep 2011 15:58:15 -0400 tstools (1.11-1) unstable; urgency=low * Initial release (Closes: #505304) -- Lorenzo Granai Tue, 11 Nov 2008 15:47:39 +0000 tstools-1.13~git20151030/debian/compat000066400000000000000000000000021261471605300173100ustar00rootroot000000000000009 tstools-1.13~git20151030/debian/control000066400000000000000000000026621261471605300175230ustar00rootroot00000000000000Source: tstools Section: utils Priority: optional Maintainer: John Cox Build-Depends: debhelper (>= 9.0.0) Standards-Version: 3.9.5 Homepage: https://github.com/kynesim/tstools.git Vcs-Git: https://github.com/kynesim/tstools.git #Vcs-Browser: http://git.debian.org/?p=collab-maint/tstools.git;a=summary Package: tstools Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: set of tools for reporting on and manipulating MPEG data TStools is a set of cross-platform command line tools for working with MPEG data. . The emphasis is on relatively simple tools which concentrate on MPEG (H.264 and H.262) data packaged according to H.222 (i.e., TS or PS), with a particular interest in checking for conformance. . Transport Stream (TS) is typically used for distribution of cable and satellite data. Program Stream (PS) is typically used to store data on DVDs. . The tools are focussed on: * Quick reporting of useful data (tsinfo, stream_type) * Giving a quick overview of the entities in the stream (esdots, psdots) * Reporting on TS packets (tsreport) or ES units/frames/fields (esreport) * Simple manipulation of stream data (es2ts, esfilter, esreverse, esmerge, ts2es) * Streaming of data, possibly with introduced errors (tsplay) #Package: tstools-doc #Architecture: all #Depends: ${shlibs:Depends}, ${misc:Depends} #Description: documentation for tstools # The documentation for tstools tstools-1.13~git20151030/debian/copyright000066400000000000000000000013151261471605300200450ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: tstools Source: https://github.com/kynesim/tstools.git Files: * Copyright: 2015, 2015, Amino Communications Ltd. License: MPL-1.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ . Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. tstools-1.13~git20151030/debian/rules000066400000000000000000000003611261471605300171670ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 # There must be a better way of doing this... override_dh_auto_install: dh_auto_install -- prefix=/usr mandir=/usr/share/man %: dh $@ tstools-1.13~git20151030/debian/source/000077500000000000000000000000001261471605300174125ustar00rootroot00000000000000tstools-1.13~git20151030/debian/source/format000066400000000000000000000000141261471605300206200ustar00rootroot000000000000003.0 (quilt) tstools-1.13~git20151030/docs/000077500000000000000000000000001261471605300156205ustar00rootroot00000000000000tstools-1.13~git20151030/docs/ac3.txt000066400000000000000000000146441261471605300170400ustar00rootroot00000000000000=== AC3 === Specification taken from a_52a.pdf I *think* that looking at the start of `syncinfo` should give me all the information I need to (a) determine if this is AC3 and (b) determine if it is "main" audio. :: syncframe: -- synchronization frame syncinfo: -- synchronization info 16: syncword -- 0x0B77 16: crc1 2: fscod -- [1] 6: frmsizecod bsi: -- bit stream info 5: bsid -- 8 in *this* version of the standard [2] 3: bsmod -- [3] 3: acmod -- which main service channels are in use [4] 2: cmixlev -- center mix level (if 3 front channels) 2: surmixlev -- surround mix level (if surround sound channel) 2: dsurmod -- dolby surround mode (if in 2/0 mode) 2=surround 1: lfeon 5: dialnorm 1: compre for n in range(6): audblk: -- coded audio block (256 new audio samples per channel) auxdata: errorcheck: -- CRC for whole syncframe 1: crcsv 16: crc2 [1] fscod: sampling rate in kHz: 00 = 48 01 = 44.1 10 = 32 11 = reserved [2] bsid: values less than 8 are for subsets of the standard. If the software can decode data with bsid=8, it can also decode data with bsid<8. [3] bit stream mode: (the "full" column gives the full service flag for use in DVB's AC-3_descriptor:AC-3_type [5]) bsmod acmod type of service full? 000 any main audio service: complete main (CM) 1 001 any main audio service: music & effects (ME) 0 010 any associated service: visually impaired (VI) X 011 any associated service: hearing impaired (HI) X 100 any associated service: dialogue (D) 0 101 any associated service: commentary (C) X 110 any associated service: emergency (E) 1 111 001 associated service: voice over (VO) 0 111 010-111 main audio service: karaoke 1 [4] audio coding mode: bit meaning 0 center channel in use 1 2 surround sound channels in use audio full coding bandwidth acmod mode chans order 0 1+1 2 Ch1,Ch2 ("dual mono") 1 1/0 1 C 2 2/0 2 L,R 3 3/0 3 L,C,R 4 2/1 3 L,R,S 5 3/1 4 L,C,R,S 6 2/2 4 L,R,SL,SR 7 3/2 5 L,C,R,SL,SR AC3 in TS ========= AC sync frame contains 1536 audio samples. Its duration is:: 48kHz -> 32ms 44.1kHz -> approx 34.83ms 32 kHz -> 48ms For ATSC:: stream_type 0x81 stream_id 0xBD (private_stream_1) in PES header registration_descriptor: (in PMT) 8: descriptor_tag -- 0x05 8: descriptor_length -- 0x04 32: format_identifier -- 0x41432D33 ("AC-3") audio_stream_descriptor: (in PSI) 8: descriptor_tag -- 0x81 8: descriptor_length -- 3: sample_rate_code 5: bsid -- same as bsid above [2] 6: bit_rate_code 2: surround_mode 3: bsmod -- same as bsmod above [3] 4: num_channels 1: full_svc ---------------- further optional fields, depending on the above ISO_639_language_code descriptor allows a stream to be tagged with the 24-bit ISO 639 language code. For DVB:: stream_type 0x06 (private_data) stream_id 0xBD (private_stream_1) in PES header AC-3_descriptor: (in PSI and PMT) 8: descriptor_tag -- 0x6A 8: descriptor_length -- 1: AC-3_type_flag 1: bsid_flag 1: mainid_flag 1: asvc_flag 4: ---------------- further fields present if their flag is set 8: AC-3_type -- [5] 8: bsid -- same as bsid above [2] 8: mainid -- 0-7 main audio service id 8: asvc -- associate service with main service ---------------- further info to the number of bytes indicated n*8: additional info [5] I *think* this is interpreted as follows: Bits Meaning 0-2 0: mono 1: 1+1 2: 2 channel (stereo) 3: 2 channel Dolby surround encoded (stereo) 4: Multichannel audio (>2 channels) Other values reserved 3-5 same as [3], bit stream mode 6 0: Use channel in combination with another 1: Full service channel, use alone 7 Must be 0 .. ***** BEGIN LICENSE BLOCK ***** License ------- Version: MPL 1.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is the MPEG TS, PS and ES tools. The Initial Developer of the Original Code is Amino Communications Ltd. Portions created by the Initial Developer are Copyright |copy| 2008 the Initial Developer. All Rights Reserved. .. |copy| unicode:: 0xA9 .. copyright sign Contributor(s): Amino Communications Ltd, Swavesey, Cambridge UK .. ***** END LICENSE BLOCK ***** .. ------------------------------------------------------------------------------- .. vim: set filetype=rst expandtab shiftwidth=2: tstools-1.13~git20151030/docs/adts.txt000066400000000000000000000036461261471605300173250ustar00rootroot00000000000000ADTS ==== :: frame: byte alignment fixed header variable header error check for (ii = 0; ii < no raw blocks in frame + 1; ii++) raw data block fixed header: (the same for all frames) syncword 12 ('1111 1111 1111') 12 id 1 (1=MPEG-2 AAC, 0=MPEG-4) layer 2 ('00') protection absent 1 (see below) profile objecttype 2 sampling freq index 4 private bit 1 (not used) channel config 3 original/copy 1 home 1 16 emphasis 2 (maybe only if id=1?) variable header: copyright id bit 1 copyright id start 1 aac frame length 13 (length of whole frame, inc. headers) adts buffer fullness 11 no raw data blocks in frame 2 (0=>1 raw data block) error check: if protection absent == 0: crc check 16 .. ***** BEGIN LICENSE BLOCK ***** License ------- Version: MPL 1.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is the MPEG TS, PS and ES tools. The Initial Developer of the Original Code is Amino Communications Ltd. Portions created by the Initial Developer are Copyright |copy| 2008 the Initial Developer. All Rights Reserved. .. |copy| unicode:: 0xA9 .. copyright sign Contributor(s): Amino Communications Ltd, Swavesey, Cambridge UK .. ***** END LICENSE BLOCK ***** .. ------------------------------------------------------------------------------- .. vim: set filetype=rst expandtab shiftwidth=2: tstools-1.13~git20151030/docs/build_html.py000077500000000000000000000031561261471605300203250ustar00rootroot00000000000000#! /usr/bin/env python """Build HTML from the reStructuredText files in this directory. This is a script just so I don't have to remember the particular incantation required. It's not in the Makefile because I'm not yet sure it belongs there... Requires Python and docutils. Uses rst2html.py on individual files because that seems to be available more often than the buildhtml.py script. """ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Amino Communications Ltd, Swavesey, Cambridge UK # # ***** END LICENSE BLOCK ***** import os def main(): filenames = os.listdir('.') for name in filenames: base,ext = os.path.splitext(name) if ext == '.txt': print 'Processing',name os.system('rst2html --stylesheet-path=default.css' ' --embed-stylesheet %s > %s'%(name,base+'.html')) if __name__ == "__main__": main() tstools-1.13~git20151030/docs/default.css000066400000000000000000000077431261471605300177710ustar00rootroot00000000000000/* :Author: David Goodger :Contact: goodger@users.sourceforge.net :date: $Date: 2004/08/02 13:51:25 $ :version: $Revision: 1.1 $ :copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. */ .first { margin-top: 0 } .last { margin-bottom: 0 } a.toc-backref { text-decoration: none ; color: black } blockquote.epigraph { margin: 2em 5em ; } dd { margin-bottom: 0.5em } /* Uncomment (& remove this text!) to get bold-faced definition list terms dt { font-weight: bold } */ div.abstract { margin: 2em 5em } div.abstract p.topic-title { font-weight: bold ; text-align: center } div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning, div.admonition { margin: 2em ; border: medium outset ; padding: 1em } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: red ; font-weight: bold ; font-family: sans-serif } div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title, div.admonition p.admonition-title { font-weight: bold ; font-family: sans-serif } div.dedication { margin: 2em 5em ; text-align: center ; font-style: italic } div.dedication p.topic-title { font-weight: bold ; font-style: normal } div.figure { margin-left: 2em } div.footer, div.header { font-size: smaller } div.sidebar { margin-left: 1em ; border: medium outset ; padding: 0em 1em ; background-color: #ffffee ; width: 40% ; float: right ; clear: right } div.sidebar p.rubric { font-family: sans-serif ; font-size: medium } div.system-messages { margin: 5em } div.system-messages h1 { color: red } div.system-message { border: medium outset ; padding: 1em } div.system-message p.system-message-title { color: red ; font-weight: bold } div.topic { margin: 2em } h1.title { text-align: center } h2.subtitle { text-align: center } hr { width: 75% } ol.simple, ul.simple { margin-bottom: 1em } ol.arabic { list-style: decimal } ol.loweralpha { list-style: lower-alpha } ol.upperalpha { list-style: upper-alpha } ol.lowerroman { list-style: lower-roman } ol.upperroman { list-style: upper-roman } p.attribution { text-align: right ; margin-left: 50% } p.caption { font-style: italic } p.credits { font-style: italic ; font-size: smaller } p.label { white-space: nowrap } p.rubric { font-weight: bold ; font-size: larger ; color: maroon ; text-align: center } p.sidebar-title { font-family: sans-serif ; font-weight: bold ; font-size: larger } p.sidebar-subtitle { font-family: sans-serif ; font-weight: bold } p.topic-title { font-weight: bold } pre.address { margin-bottom: 0 ; margin-top: 0 ; font-family: serif ; font-size: 100% } pre.line-block { font-family: serif ; font-size: 100% } pre.literal-block, pre.doctest-block { margin-left: 2em ; margin-right: 2em ; background-color: #eeeeee } span.classifier { font-family: sans-serif ; font-style: oblique } span.classifier-delimiter { font-family: sans-serif ; font-weight: bold } span.interpreted { font-family: sans-serif } span.option { white-space: nowrap } span.option-argument { font-style: italic } span.pre { white-space: pre } span.problematic { color: red } table { margin-top: 0.5em ; margin-bottom: 0.5em } table.citation { border-left: solid thin gray ; padding-left: 0.5ex } table.docinfo { margin: 2em 4em } table.footnote { border-left: solid thin black ; padding-left: 0.5ex } td, th { padding-left: 0.5em ; padding-right: 0.5em ; vertical-align: top } th.docinfo-name, th.field-name { font-weight: bold ; text-align: left ; white-space: nowrap } h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { font-size: 100% } tt { background-color: #eeeeee } ul.auto-toc { list-style-type: none } tstools-1.13~git20151030/docs/get_next_access_unit.py000066400000000000000000000166451261471605300224030ustar00rootroot00000000000000# Pseudo-Python rendition of the code for ``get_next_access_unit()``. # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Amino Communications Ltd, Swavesey, Cambridge UK # # ***** END LICENSE BLOCK ***** def get_next_access_unit(context): """Retrieve the next access unit from the file described by `context`. """ access_unit = build_access_unit() if context.pending_nal: # i.e., we already had a NAL to start this unit access_unit.append(context.pending_nal,TRUE,context.pending_list) context.pending_nal = NULL context.pending_list.reset(FALSE) while 1: try: nal = context.find_next_NAL_unit() except EOF: context.no_more_data = TRUE; # prevent future reads on this stream break except BrokenNALUnit: WARNING("!!! Ignoring broken NAL unit\n") access_unit.ignored_broken_NAL_units += 1 continue if nal.is_slice(): if not access_unit.started_primary_picture: # We're in a new access unit, but we haven't had a slice # yet, so we can be lazy and assume that this must be the # first slice nal.start_reason = "First slice of new access unit" access_unit.append(nal,TRUE,context.pending_list) context.pending_list.reset(FALSE) context.remember_earlier_primary_start(nal) elif nal.is_first_VCL_NAL(context.earlier_primary_start): # Regardless of what we determine next, we need to remember # that the NAL started (what may later be the previous) access # unit context.remember_earlier_primary_start(nal) if access_unit.started_primary_picture: # We were already in an access unit with a primary # picture, so this NAL unit must start a new access unit. # Remember it for next time, and return the access unit so # far. context.pending_nal = nal break; # Ready to return the access unit else: # This access unit was waiting for its primary picture access_unit.append(nal,TRUE,context.pending_list) context.pending_list.reset(FALSE) elif not access_unit.started_primary_picture: # But this is not a NAL unit that may start a new # access unit. So what should we do? Ignore it? if not quiet: WARNING("!!! Ignoring VCL NAL that cannot start a new" " primary picture: " nal.report(stderr) elif nal_is_redundant(nal): # printf(" ignoring redundant NAL unit\n") pass else: # We're part of the same access unit, but not special access_unit.append(nal,FALSE,context.pending_list) context.pending_list.reset(FALSE) elif nal.nal_unit_type == NAL_ACCESS_UNIT_DELIM: # An access unit delimiter always starts a new access unit if access_unit.started_primary_picture: context.pending_list.append(nal) break # Ready to return the "previous" access unit else: # The current access unit doesn't yet have any VCL NALs if context.pending_list.length > 0: WARNING("!!! Ignoring items after last VCL NAL and" " before Access Unit Delimiter\n") context.pending_list.report(stderr," ",NULL,) context.pending_list.reset(TRUE) if access_unit.nal_units.length > 0: WARNING("!!! Ignoring incomplete access unit\n") access_unit.nal_units.report(stderr," ",NULL,) access_unit.nal_units.reset(TRUE) access_unit.append(nal,FALSE,NULL) elif nal.nal_unit_type == NAL_SEI: # SEI units always precede the primary coded picture # - so they also implicitly end any access unit that has already # started its primary picture if access_unit.started_primary_picture: context.pending_list.append(nal) break # Ready to return the "previous" access unit else: context.pending_list.append(nal) elif nal.nal_unit_type in [NAL_SEQ_PARAM_SET, NAL_PIC_PARAM_SET, 13, 14, 15, 16, 17, 18]: # These start a new access unit *if* they come after the last VCL # NAL of an access unit. But we can only *tell* that they are # after the last VCL NAL of an access unit when we start the next # access unit - so we need to hold them in hand until we know that # we need them. (i.e., they'll get added to an access unit just # before the next "more determined" NAL unit we add to an access # unit) context.pending_list.append(nal) elif nal.nal_unit_type == NAL_END_OF_SEQ: if context.pending_list.length > 0: WARNING("!!! Ignoring items after last VCL NAL and" " before End of Sequence\n") context.pending_list.report(stderr," ",NULL,) context.pending_list.reset(TRUE) # And remember this as the End of Sequence marker context.end_of_sequence = nal break elif nal.nal_unit_type == NAL_END_OF_STREAM: # And remember this as the End of Stream marker context.end_of_stream = nal # Which means there's no point in reading more from this stream # (setting no_more_data like this means that *next* time this # function is called, it will return EOF) context.no_more_data = TRUE # And we're done break else: # It's not a slice, or an access unit delimiter, or an # end of sequence or stream, or a sequence or picture # parameter set, or various other odds and ends, so it # looks like we can ignore it. pass # Check for an immediate "end of file with no data" # - i.e., we read EOF or end of stream, and there was nothing # between the last access unit and such reading if context.no_more_data and access_unit.nal_units.length == 0: raise EOF # Otherwise, finish off and return the access unit we have in hand access_unit.end(context,show_details) # Remember to count it context.access_unit_index += 1 return access_unit tstools-1.13~git20151030/docs/get_next_access_unit.py.html000066400000000000000000000564131261471605300233430ustar00rootroot00000000000000 get_next_access_unit.py.html # Pseudo-Python rendition of the code for ``get_next_access_unit()``.

# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the MPEG TS, PS and ES tools.
#
# The Initial Developer of the Original Code is Amino Communications Ltd.
# Portions created by the Initial Developer are Copyright (C) 2008
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#   Amino Communications Ltd, Swavesey, Cambridge UK
#
# ***** END LICENSE BLOCK *****

def get_next_access_unit(context):
    """Retrieve the next access unit from the file described by `context`.
    """
    access_unit = build_access_unit()
    if context.pending_nal: # i.e., we already had a NAL to start this unit
        access_unit.append(context.pending_nal,TRUE,context.pending_list)
        context.pending_nal = NULL
        context.pending_list.reset(FALSE)
    
    while 1:
        try:
            nal = context.find_next_NAL_unit()
        except EOF:
            context.no_more_data = TRUE; # prevent future reads on this stream
            break
        except BrokenNALUnit:
            WARNING("!!! Ignoring broken NAL unit\n")
            access_unit.ignored_broken_NAL_units += 1
            continue

        if nal.is_slice():
            if not access_unit.started_primary_picture:
                # We're in a new access unit, but we haven't had a slice
                # yet, so we can be lazy and assume that this must be the
                # first slice
                nal.start_reason = "First slice of new access unit"
                access_unit.append(nal,TRUE,context.pending_list)
                context.pending_list.reset(FALSE)
                context.remember_earlier_primary_start(nal)
            elif nal.is_first_VCL_NAL(context.earlier_primary_start):
                # Regardless of what we determine next, we need to remember
                # that the NAL started (what may later be the previous) access
                # unit
                context.remember_earlier_primary_start(nal)
                if access_unit.started_primary_picture:
                    # We were already in an access unit with a primary
                    # picture, so this NAL unit must start a new access unit.
                    # Remember it for next time, and return the access unit so
                    # far.
                    context.pending_nal = nal
                    break;    # Ready to return the access unit
                else:
                    # This access unit was waiting for its primary picture
                    access_unit.append(nal,TRUE,context.pending_list)
                    context.pending_list.reset(FALSE)
            elif not access_unit.started_primary_picture:
                # But this is not a NAL unit that may start a new
                # access unit. So what should we do? Ignore it?
                if not quiet:
                    WARNING("!!! Ignoring VCL NAL that cannot start a new"
                            " primary picture: "
                    nal.report(stderr)
            elif nal_is_redundant(nal):
                # printf("     ignoring redundant NAL unit\n")
                pass
            else:
                # We're part of the same access unit, but not special
                access_unit.append(nal,FALSE,context.pending_list)
                context.pending_list.reset(FALSE)
        elif nal.nal_unit_type == NAL_ACCESS_UNIT_DELIM:
            # An access unit delimiter always starts a new access unit
            if access_unit.started_primary_picture:
                context.pending_list.append(nal)
                break # Ready to return the "previous" access unit
            else:
                # The current access unit doesn't yet have any VCL NALs
                if context.pending_list.length > 0:
                    WARNING("!!! Ignoring items after last VCL NAL and"
                                " before Access Unit Delimiter\n")
                    context.pending_list.report(stderr,"    ",NULL,)
                    context.pending_list.reset(TRUE)
                if access_unit.nal_units.length > 0:
                    WARNING("!!! Ignoring incomplete access unit\n")
                    access_unit.nal_units.report(stderr,"    ",NULL,)
                    access_unit.nal_units.reset(TRUE)
                access_unit.append(nal,FALSE,NULL)
        elif nal.nal_unit_type == NAL_SEI:
            # SEI units always precede the primary coded picture
            # - so they also implicitly end any access unit that has already
            # started its primary picture
            if access_unit.started_primary_picture:
                context.pending_list.append(nal)
                break # Ready to return the "previous" access unit
            else:
                context.pending_list.append(nal)
        elif nal.nal_unit_type in [NAL_SEQ_PARAM_SET, NAL_PIC_PARAM_SET,
                                   13, 14, 15, 16, 17, 18]:
            # These start a new access unit *if* they come after the last VCL
            # NAL of an access unit. But we can only *tell* that they are
            # after the last VCL NAL of an access unit when we start the next
            # access unit - so we need to hold them in hand until we know that
            # we need them.  (i.e., they'll get added to an access unit just
            # before the next "more determined" NAL unit we add to an access
            # unit)
            context.pending_list.append(nal)
        elif nal.nal_unit_type == NAL_END_OF_SEQ:
          if context.pending_list.length > 0:
            WARNING("!!! Ignoring items after last VCL NAL and"
                    " before End of Sequence\n")
            context.pending_list.report(stderr,"    ",NULL,)
            context.pending_list.reset(TRUE)
            # And remember this as the End of Sequence marker
            context.end_of_sequence = nal
            break
        elif nal.nal_unit_type == NAL_END_OF_STREAM:
            # And remember this as the End of Stream marker
            context.end_of_stream = nal
            # Which means there's no point in reading more from this stream
            # (setting no_more_data like this means that *next* time this
            # function is called, it will return EOF)
            context.no_more_data = TRUE
            # And we're done
            break
        else:
          # It's not a slice, or an access unit delimiter, or an
          # end of sequence or stream, or a sequence or picture
          # parameter set, or various other odds and ends, so it
          # looks like we can ignore it.
          pass

    # Check for an immediate "end of file with no data"
    # - i.e., we read EOF or end of stream, and there was nothing
    # between the last access unit and such reading
    if context.no_more_data and access_unit.nal_units.length == 0:
        raise EOF
    
    # Otherwise, finish off and return the access unit we have in hand
    access_unit.end(context,show_details)

    # Remember to count it
    context.access_unit_index += 1

    return access_unit
tstools-1.13~git20151030/docs/index.txt000066400000000000000000000035221261471605300174720ustar00rootroot00000000000000====================== TS tools documentation ====================== :Tools_: Short descriptions of how to use the various tools provided. :Library_: An overview of the functionality of the library, and how it works. :get_next_access_unit.py_: The code for ``get_next_access_unit`` from ``accessunit.c`` expressed as pseudo-Python. This is the function that gathers H.264 NAL units into access units. :get_next_access_unit.py.html_: The code for ``get_next_access_unit`` from ``accessunit.c`` expressed as pseudo-Python and colourised as HTML. :`To do`_: Things still outstanding. :`ac3`_: Some notes on AC-3 .. _Tools: tools.html .. _Library: library.html .. _`To do`: todo.html .. _`ac3`: ac3.html .. _get_next_access_unit.py: get_next_access_unit.py .. _get_next_access_unit.py.html: get_next_access_unit.py.html .. ***** BEGIN LICENSE BLOCK ***** License ------- Version: MPL 1.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is the MPEG TS, PS and ES tools. The Initial Developer of the Original Code is Amino Communications Ltd. Portions created by the Initial Developer are Copyright |copy| 2008 the Initial Developer. All Rights Reserved. .. |copy| unicode:: 0xA9 .. copyright sign Contributor(s): Amino Communications Ltd, Swavesey, Cambridge UK .. ***** END LICENSE BLOCK ***** .. ------------------------------------------------------------------------------- .. vim: set filetype=rst expandtab shiftwidth=2: tstools-1.13~git20151030/docs/library.txt000066400000000000000000000462721261471605300200400ustar00rootroot00000000000000================ TS tools library ================ .. contents:: Relevant International Standards ================================ - ISO/IEC 13818-1 (H.222.0) *Information technology - Generic coding of moving pictures and associated audio information: Systems* This describes: - TS (Transport Stream) - PS (Program Stream) - ES (Elementary Stream) and - PES (Packetised Elementary Stream) which form the transport layers for the following standards. - ISO/IEC 13818-2 (H.262) *Information technology - Generic coding of moving pictures and associated audio information: Video* This defines MPEG-2. - ISO/IEC 14496-10 (H.264) This defines MPEG-4/AVC. Overview of modules =================== Standalone header files: :compat.h: Defines useful types for portability between Unices and Windows (for instance, the basic integer types and ``offset_t``, which is a 64 or 32 bit file offset as appropriate). :h222_defns.h: Defines various values useful when using H.222. Source files: :accessunit.c: Handling H.264 access units, including reading them in as NAL units. :adts.c: Some minimal support for ISO/IEC 14496-3:2001(E) AAC ADTS audio streams - basically what is needed by the `esmerge` tool. :bitdata.c: Handling bit level data, including reading Exp-Golomb encoded values. Used by the NAL unit reading functions in nalunit.c. :es.c: Reading and writing at an Elementary Stream level. :filter.c: Fast forward algorithms. :h262.c: Handling H.262 pictures. :misc.c: As it says, various things that provide miscellaneous support. :nalunit.c: Handling H.264 NAL units, mainly as a base for access units. :pes.c: Reading PS or TS data, and extracting PES therefrom. Used as a level under es.c to allow reading of ES data from PS and TS files. :pidint.c: Handling "dictionaries" of PIDs versus integers (for instance, PID and program stream). :ps.c: Provides the ``ps_to_ts`` function, which forms the basis of the ps2ts tool. :reverse.c: Reversing algorithms and support. :ts.c: Reading and writing Transport Stream. :tswrite.c: Support for writing Transport Stream packets, either to a file, over TCP/IP or (via a circular buffer) over UDP. This thus provides support for all tools that have a ``-host`` switch, and also the bulk of the functionality of tsplay. Also provides the code that allows tsserve to read command characters from a socket. Each source file *xxx* also has associated with it a header file defining datastructures, constants and macros, called *xxx*\ _defns.h, and a header file detailing ``extern`` functions therefrom, called *xxx*\ _fns.h. The latter will always include the former. The documentation for each ``extern`` function is reproduced in the header file, directly copied from the source. This is done for the convenience of the user, but if any discrepancy occurs, the version of the functionc header comment in the source file should be taken as correct. Not all ``extern`` functions are intended for use by end-users. Some are really only used within the library itself. Unfortunately, these functions are not flagged as such at the moment. Reading data ============ In general, the various MPEG entities are not read directly from a file, but through a context datastructure. For instance, reading an access unit may stop before a particular NAL unit, which thus forms the start of the next access unit, and NAL units themselves need to be interpreted in the context of sequence and picture parameter sets. These are arranged roughly as follows:: +-------------------+ (r) +---------------+ | H.264 access unit | | H.262 context | | context | +---------------+ +-------------------+ : : : : : +------------------+ (*) : | NAL unit context | : +------------------+ : : : : : +---------------------------+ | ES context | +---------------------------+ : : : : +------------------+ : | PES reader | : +------------------+ : : : : +-----------+ +-----------+ : | TS reader | | PS reader | : +-----------+ +-----------+ : : : : : : : +---------------------------+ | File | +---------------------------+ :(r): Both H.264 and H.262 contexts can be associated with a "reversing" context, to accumulate data for outputting the stream in (fast) reverse. :(*): A NAL unit context is created implicitly when building an access unit context "over" an ES context. Access units, H.264, MPEG-4/AVC ------------------------------- An access unit context is explicitly built on top of an ES context:: err = build_access_unit_context(es,&acontext); free_access_unit_context(&acontext); Freeing the access unit context does not free the ES context. As well as maintaining the information to allow reading access units, the context also remembers any trailing (end of sequence or end of stream) NAL units. This is mostly transparent to the user, but is explained in the appropriate function header comments. An individual access unit can be retrieved:: err = get_next_access_unit(acontext,quiet,show_details,&access_unit); but it is more normal to retrieve a frame:: err = get_next_h264_frame(acontext,quiet,show_details,&frame); If the frame was composed of two access units (i.e., two fields), then the NAL units for the second will have been appended to the first, which is returned, and its field/frame indicator will have been set to "frame". Regardless, the same function is used to free the resultant datastructure:: free_access_unit(&frame); Access units may be written to ES or TS:: err = write_access_unit_as_ES(access_unit,context,filedesc); err = write_access_unit_as_TS(access_unit,context,tswriter,video_pid); Note that the latter assumes that the video stream id is 0xE0. Variants are alsp provided to output PTS and/or PCR values for the first PES packet written out. A report on the content of an access unit can be obtained with:: report_access_unit(filedesc,access_unit); Various utility functions are provided to investigate the properties of a particular access unit:: all_I = all_slices_I(access_unit); all_P = all_slices_P(access_unit); all_IP = all_slices_I_or_P(access_unit); all_B = all_slices_B(access_unit); Lastly, an access unit context can be rewound with:: err = rewind_access_unit_context(acontext); H.262 pictures, MPEG-2 and MPEG-1 --------------------------------- For most purposes, MPEG-1 data is supported as a subset of MPEG-2. An H.262 context is explicitly built on top of an ES context:: err = build_h262_context(es,&context); free_h262_context(&context); Freeing the H.262 context does not free the ES context. An individual H.262 picture can be retrieved:: err = get_next_h262_single_picture(context,verbose,&picture); but it is more normal to retrieve a frame:: err = get_next_h262_frame(context,verbose,quiet,&frame); If the frame was composed of two field pictures, then the H.262 items for the second will have been appended to the first, which is returned, and its field/frame indicator will have been set to "frame". Regardless, the same function is used to free the resultant datastructure:: free_h262_picture(&frame); Pictures may be written to ES or TS:: err = write_h262_picture_as_ES(filedesc,picture); err = write_h262_picture_as_TS(tswriter,picture,video_pid); Note that the latter assumes that the video stream id is 0xE0. A report on the content of a picture can be obtained with:: report_h262_picture(filedesc,picture,report_data); Lastly, an H.262 context can be rewound with:: err = rewind_h262_context(context); Below the picture level ----------------------- H.264 access units are composed from NAL units, read with an underlying NAL unit context (which is created automatically within an access unit context). The NAL unit context is then retrievable as ``acontext->nac``. A NAL unit context may also be created (and then freed) directly:: err = build_nal_unit_context(es,&context); free_nal_unit_context(context); The NAL unit context remembers the picture and sequence parameter sets for the H.264 data stream. From whatever source, the NAL unit context can be used to read NAL units directly (although doing this with the ``nac`` from an access unit context will disrupt access unit reading):: err = find_next_NAL_unit(context,verbose,&nal); free_nal_unit(&nal); Functions also exist to report on an individual NAL unit, and to write it out as ES or TS data. H.262 pictures are composed of individual units as well, although there does not appear to be a standard name for these. The H.262 context manages their reading directly, and they may also be read individually (although doing so will disrupt H.262 picture reading):: err = find_next_h262_item(es,&item); Again, functions are provided to report on such an item, or write it out as ES or TS. Each NAL unit or MPEG-2 item contains a single ES unit (which is why the contexts used to read them and their higher level data constructs require an ES context). Elementary Stream data ---------------------- Various ways are provided to open an Elementary Stream. The simplest opens a file containing "bare" ES data:: err = open_elementary_stream(filename,&es); If a PES reader is available (for reading TS or PS data), then an elementary stream can be constructed atop that:: err = build_elementary_stream_PES(pes_reader,&es); Once the elementary stream is available, however, its underlying form does not matter, and it can normally be closed with:: close_elementary_stream(&es); (this will not "close" a PES reader if one is involved). Functions are then provided to read in individual ES units, although in practice the higher level (H.264 access unit and H.262 picture) functions will be used to read data. PES reading - TS and PS data ---------------------------- PES data may be encapsulated as either PS or TS. The normal way to open a PES reader is with:: err = open_PES_reader(filename,give_info,give_warnings,&reader); which will inspect the start of the file to work out if it is PS or TS. Alternatively, if it is known which the file is, then one can directly call:: err = open_PES_reader_for_PS(filename,give_info,give_warnings,&reader); err = open_PES_reader_for_TS(filename,program_number, give_info,give_warnings,&reader); (the latter must also be used if one wants a different program number than the "first found" in TS data). The function:: err = determine_if_TS_file(filedesc,&is_TS); may also be used to figure out if an already opened file is TS, and that may then be wrapped in a reader:: err = build_PES_reader(filedesc,is_TS,give_info,give_warnings, program_number,&reader); If a PS or TS reader context is already built, then they may be wrapped within a PES reader:: err = build_TS_PES_reader(tsreader,give_info,give_warnings,program_number, &reader); err = build_PS_PES_reader(psreader,give_info,give_warnings,&reader); When finished with, the PES reader may be freed or closed (the latter also closes the PS/TS reader and underlying file):: err = free_PES_reader(&reader); err = close_PES_reader(&reader); It is possible to request that only video be read from the reader:: set_PES_reader_video_only(reader); or that audio be taken from Private Stream 1 (normally used for Dolby), as opposed to the "normal" audio streams:: set_PES_reader_audio_private1(reader); For PS data, which does not have PAT/PMT packets to describe the program being read, it is possible to set various key pieces of information:: set_PES_reader_program_data(reader,program_number,pmt_pid, video_pid,audio_pid,pcr_pid); In situations where the software has "guessed" wrongly whether the data is H.262 or H.264, or where data is being read from standard input and it did not have an opportunity to decide, it is possible to insist:: set_PES_reader_h264(reader); (the default is H.262). PES packets may be read individually, but this is normally mediated by one of the higher levels. Server mode ........... It is possible to associate a Transport Stream writer with the PES input stream. This is then used to "mirror" each PES packet, so that the input stream is automatically written out as TS (specifically, each time a new PES packet is read in, the previous packet is written out). Where to write the data is specified with:: set_server_output(reader,tswriter,program_freq); This also starts the mirroring. ``program_freq`` is how often (in PES packets) the PAT/PMT program information should be written out. Mirroring may be switched on and off using:: start_server_output(reader); stop_server_output(reader); ``tsserve`` is the main program that takes advantage of this capability - using it whilst moving linearly forwards in the data is simple enough, but if one needs to fast forwards or move backwards, things rapidly become more complex. Read-ahead buffers ================== Since the bottom-most file access is done via file descriptors, there is no system-provided buffering. Currently, read-ahead buffers are provided by: * The TS reader * The "bare" ES reader (i.e., reading bytes directly from a file) In both of these contexts, ``ftell`` cannot usefully be used to determine where in the file the application is/will be reading - instead, the TS reader context and ES context maintain their own notions of current position, which should be used instead. Rewinding ========= As a rule, when rewinding a data stream, use the rewind function for the "highest level" context available. Thus if reading access units, use ``rewind_access_unit_context``, rather than (for instance) ``seek_ES``. General seeking within files above the ES level has not been implemented, as none of the existing tools require it. Reversing ========= For issues when reversing H.262 data, see the documentation for ``esreverse`` in the Tools_ document. .. _Tools: tools.html Reversing of H.264 currently uses non-IDR frames more than it should. This is primarily because the Harry Potter clip only has a single IDR, and thus it has been difficult to be sure what to do. Unfortunately, in H.264, B and P frames can refer back before the last I frame, so just outputting a couple of reference frames does not guarantee a coherent picture when the next non-reference frame is encountered. The solution is to enfore output of IDR frames at such transitions, and this will be investigated later on. Reversing in the library is handed in a relatively "black box" manner. A reverse data context must be built:: err = build_reverse_data(&reverse_data,is_h264); and then added to the appropriate H.262 picture or H.264 access unit context:: err = add_h262_reverse_context(context,reverse_data); err = add_access_unit_reverse_context(acontext,reverse_data); (this could obviously use some streamlining). After this, normal reading of frames in the forwards direction remembers appropriate reversing information. Alternatively, the reversing data for a whole file can be accumulated with one call (it just processes through the file):: err = collect_reverse_h262(context,max,verbose,quiet); err = collect_reverse_access_units(acontext,max,verbose,quiet, seq_param_data,pic_param_data); Data may be output in reverse using the appropriate call - these are the same for H.262 and H.264 data:: err = output_in_reverse_as_ES(es,filedesc,freqency,verbose,quiet, start_with,max,reverse_data); err = output_in_reverse_as_TS(es,tswriter,verbose,quiet,offset, start_with,max,reverse_data); ``start_with`` indicates which frame to start reversing from - ``-1`` means the "current" picture. ``frequency`` indicates the speed of reversing required - thus a value of ``8`` means reversing at (about) 8 times. The reversing datastructures can be freed when no longer needed:: free_reverse_data(reverse_data); but this does not detach them from the H.262 or H.264 context, so should only be used when tidying up those datastructures. Filtering ========= For issues when filtering data, see the documentation for ``esfilter`` in the Tools_ document. .. _Tools: tools.html An appropriate filter context is built:: err = build_h262_filter_context_strip(&fcontext,context,all_IP); err = build_h262_filter_context(&fcontext,context,frequency); err = build_h264_filter_context_strip(&fcontext,acontext,all_ref); err = build_h264_filter_context(&fcontext,acontext,frequency); and later freed:: free_h262_filter_context(fcontext); free_h264_filter_context(fcontext); For the stripping contexts, the ``all_IP`` flag means keep all I *and* P frames (rather than just I), and the ``all_ref`` flag means keep all reference pictures. For the filtering contexts, the ``frequency`` is the speedup that is required - for instance, a value of ``8`` means that 8x fast forward is desired. These may then be used to retrieve the next appropriate frame from the input stream:: err = get_next_stripped_h262_frame(fcontext,verbose,quiet, &seq_hdr,&frame,&frames_seen); err = get_next_filtered_h262_frame(fcontext,verbose,quiet, &seq_hdr,&frame,&frames_seen); err = get_next_stripped_h264_frame(fcontext,verbose,quiet, &frame,&frames_seen); err = get_next_filtered_h264_frame(fcontext,verbose,quiet, &frame,&frames_seen); In all cases, the caller must free ``frame`` when they have finished with it. However, for H.262 data, ``seq_hdr`` must not be freed. When filtering, ``frame`` is returned as NULL to indicate that the previous frame should be repeated, to produce (an approximation to) the desired frequency. .. ***** BEGIN LICENSE BLOCK ***** License ======= Version: MPL 1.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is the MPEG TS, PS and ES tools. The Initial Developer of the Original Code is Amino Communications Ltd. Portions created by the Initial Developer are Copyright |copy| 2008 the Initial Developer. All Rights Reserved. .. |copy| unicode:: 0xA9 .. copyright sign Contributor(s): Amino Communications Ltd, Swavesey, Cambridge UK .. ***** END LICENSE BLOCK ***** .. ------------------------------------------------------------------------------- .. vim: set filetype=rst expandtab shiftwidth=2: tstools-1.13~git20151030/docs/mdoc/000077500000000000000000000000001261471605300165425ustar00rootroot00000000000000tstools-1.13~git20151030/docs/mdoc/es2ts.1000066400000000000000000000074071261471605300176740ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt ES2TS 1 .Os .Sh NAME .Nm es2ts .Nd Convert an ES video stream to H.222 TS .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm es2ts .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl pid Ar pid_no .Op Fl pmt Ar pmt_pid_no .Op Fl host Ar host Ns Op : Ns Ar port .Op Fl max Ar max_units | Fl m Ar max_units .Ar in_file | Fl stdin .Ar out_file | Op Fl stdout .Sh DESCRIPTION Convert an elementary video stream to H.222 transport stream. Supports input streams conforming to MPEG-2 (H.262), MPEG-4/AVC (H.264) and AVS. Also supports MPEG-1 input streams, insofar as MPEG-2 is backwards compatible with MPEG-1. .Pp Note that this program works by reading and packaging the elementary stream packages directly - it does not parse them as H.262 or H.264 data. .Ss Files .Bl -tag .It Ar in_file is a file containing the Elementary Stream data (but see -stdin below) .It Ar out_file is an H.222 Transport Stream file (but see -stdout and -host below) .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output summary information about each ES packet as it is read .It Fl q , Fl quiet Only output error messages .It Fl pid Ar pid_no .Ar pid_no is the video PID to use for the data. Use '-pid 0x' to specify a hex value. .Bq default = 0x68 .It Fl pmt Ar pmt_pid_no .Ar pmt_pid_no is the PMT PID to use. Use '-pmt 0x' to specify a hex value. .Bq default = 0x66 .It Fl max Ar max_units , Fl m Ar max_units Maximum number of ES data units to read .It Fl stdin Input from standard input, instead of a file .It Fl stdout Write output to , instead of a named file Forces -quiet and -err stderr. .It Fl host Ar host Ns Op : Ns Ar port Writes output (over TCP/IP) to the named .Ar host , instead of to a named file. If .Ar port is not specified, it defaults to 88. .El .Ss Stream type When the TS data is being output, it is flagged to indicate whether it conforms to H.262, H.264 or AVS. It is important to get this right, as it will affect interpretation of the TS data. .Pp If input is from a file, then the program will look at the start of the file to determine if the stream is H.264, H.262 or AVS. This process may occasionally come to the wrong conclusion, in which case the user can override the choice using the following switches. .Pp If input is from standard input (via -stdin), then it is not possible for the program to make its own decision on the input stream type. Instead, it defaults to H.262, and relies on the user indicating if this is wrong. .Bl -tag .It Fl h264 , avc Force the program to treat the input as MPEG-4/AVC. .It Fl h262 Force the program to treat the input as MPEG-2. .It Fl avs Force the program to treat the input as AVS. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .Sh BUGS For the moment, the video input must be H.264 or AVS, and the audio input ADTS, AC-3 ATSC or MPEG layer 2. Also, the audio is assumed to have a constant number of samples per frame. tstools-1.13~git20151030/docs/mdoc/esdots.1000066400000000000000000000063411261471605300201310ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt ESDOTS 1 .Os .Sh NAME .Nm esdots .Nd Present the content of an ES as a sequence of characters .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm esdots .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl max Ar max_units | Fl m Ar max_units .Op Fl pes | ts .Op Fl hasheos .Op Fl es .Op Fl gop .Op Fl fr Ar frame_rate .Ar in_file | Fl stdin .Sh DESCRIPTION Present the content of an H.264 (MPEG-4/AVC), H.262 (MPEG-2) or AVS elementary stream as a sequence of characters, representing access units/MPEG-2 items/AVS items. .Pp (Note that for H.264 it is access units and not frames that are represented, and for H.262 it is items and not pictures.) .Ss Files .Bl -tag .It Ar in_file is an H.222 Transport Stream file (but see .Fl stdin Ns ) .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl stdin Input from standard input, instead of a file .It Fl v , Fl verbose Output extra information about packets .It Fl q , Fl quiet Only output error messages .It Fl max Ar max_units , Fl m Ar max_units Maximum number of entities to read .It Fl pes , ts The input file is TS or PS, to be read via the PES->ES reading mechanisms .It Fl hasheos Print a # on finding an EOS (end-of-stream) NAL unit rather than stopping (only applies to H.264) .It Fl es Report ES units, rather than any 'higher' unit (not necessarily suppported for all file types) .It Fl gop Show the duration of each GOP (for MPEG-2 steams) OR the distance between random access points (H.264) .It Fl fr Set the video frame rate (default = 25 fps) .El .Ss Stream type: If input is from a file, then the program will look at the start of the file to determine if the stream is H.264 or H.262 data. This process may occasionally come to the wrong conclusion, in which case the user can override the choice using the following switches. .Pp For AVS data, the program will never guess correctly, so the user must specify the file type, using -avs. .Pp If input is from standard input (via -stdin), then it is not possible for the program to make its own decision on the input stream type. Instead, it defaults to H.262, and relies on the user indicating if this is wrong. .Bl -tag .It Fl h264 , avc Force the program to treat the input as MPEG-4/AVC. .It Fl h262 Force the program to treat the input as MPEG-2. .It Fl avs Force the program to treat the input as AVS. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/esfilter.1000066400000000000000000000077571261471605300204610ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt ESFILTER 1 .Os .Sh NAME .Nm esfilter .Nd Output a filtered or truncated version of an elementary stream .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm esfilter .Fl copy | filter | strip .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl host Ar dest_ip Ns Op : Ns Ar port .Op Fl max Ar max_frames | Fl m Ar max_frames .Op Fl freq Ar keep_frequency .Op Fl allref .Op Fl tsout .Op Fl pes | ts .Op Fl h264 | avc | h262 .Ar in_file | Fl stdin .Ar out_file | Fl stdout .Sh DESCRIPTION Output a filtered or truncated version of an elementary stream. The input is either H.264 (MPEG-4/AVC) or H.262 (MPEG-2). The output is either an elementary stream, or an H.222 transport stream .Pp If output is to an H.222 Transport Stream, then fixed values for the PMT PID (0x66) and video PID (0x68) are used. .Ss Files .Bl -tag .It Ar in_file is the input elementary stream (but see .Fl stdin below). .It Ar out_file is the output stream, either an equivalent elementary stream, or an H.222 Transport Stream (but see .Fl stdout and .Fl host below). .El .Ss Actions .Bl -tag .It Fl copy Copy the input data to the output file (mostly useful as a way of truncating data with .Fl max Ns ) .It Fl filter Filter data from input to output, aiming to keep every .Ar keep_frequency Ns th frame (where .Ar keep_frequency is specified by .Fl freq Ns ). .It Fl strip For H.264, output just the IDR and I pictures, for H.262, output just the I pictures, but see .Fl allref below. .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl stdin Input from standard input, instead of a file .It Fl v , Fl verbose Output extra information about packets .It Fl q , Fl quiet Only output error messages .It Fl host Ar dest_ip Ns Op : Ns Ar port Writes output (over TCP/IP) to the named .Ar host , instead of to a named file. If .Ar port is not specified, it defaults to 88. Implies .Fl tsout . .It Fl max Ar max_frames , Fl m Ar max_frames Maximum number of frames to read (for .Fl filter and .Fl strip Ns ), or ES units/NAL units (for .Fl copy Ns ). .It Fl freq Ar keep_frequency Specify the frequency of frames to try to keep with -filter. Defaults to 8. .It Fl allref With .Fl strip , keep all reference pictures (H.264) or all I and P pictures (H.262) .It Fl tsout Output data as Transport Stream PES packets (the default is as Elementary Stream) .It Fl pes , ts The input file is TS or PS, to be read via the PES->ES reading mechanisms. Not allowed with .Fl stdin . .El .Ss Stream type: If input is from a file, then the program will look at the start of the file to determine if the stream is H.264 or H.262 data. This process may occasionally come to the wrong conclusion, in which case the user can override the choice using the following switches. .Pp If input is from standard input (via -stdin), then it is not possible for the program to make its own decision on the input stream type. Instead, it defaults to H.262, and relies on the user indicating if this is wrong. .Bl -tag .It Fl h264 , avc Force the program to treat the input as MPEG-4/AVC. .It Fl h262 Force the program to treat the input as MPEG-2. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/esmerge.1000066400000000000000000000057201261471605300202570ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt ESMERGE 1 .Os .Sh NAME .Nm esmerge .Nd Merge the contents of two ES to make a TS .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm esmerge .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl x .Op Fl h264 | avs .Op Fl vidrate Ar video_hz .Op Fl rate Ar audio_hz | Fl cd | dat .Op Fl adts | l2 | mp2adts | mp4adts | ac3 .Op Fl patpmtfreq Ar pat_freq .Ar video_file audio_file out_file .Sh DESCRIPTION Merge the contents of two Elementary Stream (ES) files, one containing video data, and the other audio, to produce an output file containing Transport Stream (TS). .Ss Files .Bl -tag .It Ar video_file is the ES file containing video. .It Ar audio_file is the ES file containing audio. .It Ar out_file is the resultant TS file. .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl stdin Input from standard input, instead of a file .It Fl v , Fl verbose Output extra information about each audio/video frame .It Fl q , Fl quiet Only output error messages .It Fl x Output diagnostic information. .It Fl h264 The video stream is H.264 (the default) .It Fl avs The video stream is AVS .It Fl vidrate Ar video_hz Video frame rate in Hz - defaults to 25Hz. .It Fl rate Ar audio_hz Audio sample rate in Hertz - defaults to 44100, i.e., 44.1KHz. .It Fl cd Equivalent to .Fl rate Cm 44100 No (CD rate), the default. .It Fl dat Equivalent to .Fl rate Cm 48000 No (DAT rate). .It Fl adts The audio stream is ADTS (the default) .It Fl l2 The audio stream is MPEG layer 2 audio .It Fl mp2adts The audio stream is MPEG-2 style ADTS regardless of ID bit .It Fl mp4adts The audio stream is MPEG-4 style ADTS regardless of ID bit .It Fl ac3 The audio stream is Dolby AC-3 in ATSC .It Fl patpmtfreq Ar pat_freq PAT and PMT will be inserted every .Ar pat_freq video frames. By default, .Ar pat_freq No = 0 and PAT/PMT are inserted only at the start of the output stream. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .Sh BUGS For the moment, the video input must be H.264 or AVS, and the audio input ADTS, AC-3 ATSC or MPEG layer 2. Also, the audio is assumed to have a constant number of samples per frame. tstools-1.13~git20151030/docs/mdoc/esreport.1000066400000000000000000000073741261471605300205020ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt ESREPORT 1 .Os .Sh NAME .Nm esreport .Nd Report on the contents of an ES .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm esmerge .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl frames | findfields | afd | es .Op Fl framesize .Op Fl frametype .Op Fl x .Op Fl max Ar max-units | Fl m Ar max_units .Op Fl pes | ts .Op Fl pesreport .Op Fl h264 | avc | h262 | avs .Ar in_file | Fl stdin .Sh DESCRIPTION Report on the content of an elementary stream containing H.264 (MPEG-4/AVC), H.262 (MPEG-2) or AVS video data. .Ss Files .Bl -tag .It Ar in_file is the Elementary Stream file (but see -stdin below) .El .Ss What to report The default is to report on H.262 items, AVS frames or H.264 NAL units. Other choices are: .Bl -tag .It Fl frames Report by frames. The default for AVS. .It Fl findfields Report on any fields in the data. Ignored for AVS. .It Fl afd Report (just) on AFD changes in H.262. Ignored for the other types of file. .It Fl es Report on ES units. .El .Pp Reporting on frames may be modified by: .Bl -tag .It Fl framesize Report on the sizes of frames (mean, etc.). .It Fl frametype Report on the numbers of different type of frame. .El .Pp (in fact, both of these imply -frame). .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl stdin Input from standard input, instead of a file .It Fl v , Fl verbose For H.262 data, output information about the data in each MPEG-2 item. For ES units, output information about the data in each ES unit. Ignored for H.264 data. .It Fl q , Fl quiet Only output summary information (i.e., the number of entities in the file, statistics, etc.) .It Fl x Show details of each NAL unit as it is read. .It Fl stdin Take input from , instead of a named file .It Fl max Ar max_units , Fl m Ar max_units Maximum number of NAL units/MPEG-2 items/AVS frames/ES units to read. If -frames, then the program will stop after that many frames. If reading 'frames', MPEG-2 and AVS will also count sequence headers and sequence end. .It Fl pes , ts The input file is TS or PS, to be read via the PES to ES reading mechanisms .It Fl pesreport Report on PES headers. Implies .Fl pes No and Fl q . .El .Ss Stream type: If input is from a file, then the program will look at the start of the file to determine if the stream is H.264, H.262 or AVS data. This process may occasionally come to the wrong conclusion, in which case the user can override the choice using the following switches. .Pp If input is from standard input (via -stdin), then it is not possible for the program to make its own decision on the input stream type. Instead, it defaults to H.262, and relies on the user indicating if this is wrong. .Bl -tag .It Fl h264 , avc Force the program to treat the input as MPEG-4/AVC. .It Fl h262 Force the program to treat the input as MPEG-2. .It Fl avs Force the program to treat the input as AVS. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/esreverse.1000066400000000000000000000062061261471605300206330ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt ESREVERSE 1 .Os .Sh NAME .Nm esreverse .Nd Report on the contents of an ES .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm esreverse .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl host Ar host Ns Op : Ns Ar port .Op Fl max Ar max_frames | Fl m Ar max_frames .Op Fl freq Ar frame_freq .Op Fl tsout .Op Fl pes |-ts .Op Fl server .Op Fl x .Op Fl h264 | avc | h262 .Ar in_file .Ar out_file | Fl stdout .Sh DESCRIPTION Output a reversed stream derived from the input H.264 (MPEG-4/AVC) or H.262 (MPEG-2) elementary stream. .Pp If output is to an H.222 Transport Stream, then fixed values for the PMT PID (0x66) and video PID (0x68) are used. .Ss Files .Bl -tag .It Ar in_file is the input elementary stream. .It Ar out_file is the output stream, either an equivalent elementary stream, or an H.222 Transport Stream (but see Fl stdout No and Fl host below). .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output additional (debugging) messages .It Fl q , Fl quiet Only output error messages .It Fl stdout Write output to , instead of a named file. Forces .Fl quiet No and Fl "err stderr". .It Fl host Ar host Ns Op : Ns Ar port Writes output (over TCP/IP) to the named , instead of to a named file. If is not specified, it defaults to 88. Implies .Fl tsout . .It Fl max Ar max_frames | Fl m Ar max_frames Maximum number of frames to read .It Fl freq Ar frame_freq Specify the frequency of frames to try to keep when reversing. Defaults to 8. .It Fl tsout Output H.222 Transport Stream .It Fl pes , ts The input file is TS or PS, to be read via the PES to ES reading mechanisms .It Fl server Also output as normal forward video as reversal data is being collected. Implies .Fl pes No and Fl tsout . .It Fl x Temporary extra debugging information .El .Ss Stream type: If input is from a file, then the program will look at the start of the file to determine if the stream is H.264 or H.262 data. This process may occasionally come to the wrong conclusion, in which case the user can override the choice using the following switches. .Bl -tag .It Fl h264 , avc Force the program to treat the input as MPEG-4/AVC. .It Fl h262 Force the program to treat the input as MPEG-2. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/m2ts2ts.1000066400000000000000000000036211261471605300201440ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt M2TS2TS 1 .Os .Sh NAME .Nm m2ts2ts .Nd convert .m2ts to .ts .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm m2ts2ts .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl buffer Ar buf_pkts | Fl b Ar buf_pkts .Ar in_file | Fl stdin .Ar out_file | Fl stdout .Sh DESCRIPTION Converts BDAV MPEG-2 Transport Stream file (M2TS) to an 'ordinary' TS file .Ss Files .Bl -tag .It Ar in_file is a BDAV MPEG-2 Transport Stream file (M2TS)(but see .Fl stdin below). .It Ar out_file is an H.222 Transport Stream file (but see .Fl stdout below). .El .Ss General switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output extra information .It Fl q , Fl quiet Only output error messages .It Fl stdin Input from standard input instead of a file .It Fl stdout Output to standard output instead of a file. Forces .Fl quiet No and Fl "err stderr" . .It Fl b Ar buf_pkts , Fl buffer Ar buf_pkts Number of TS packets to buffer for reordering .Bq default = 4 .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/pcapreport.1000066400000000000000000000140241261471605300210040ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt PCAPREPORT 1 .Os .Sh NAME .Nm pcapreport .Nd Get info about a TS in a pcap .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm pcapinfo .Fl h | help Op Cm detail .Nm pcapinfo .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl name Ar base_name | Fl n Ar base_name .Op Fl extract | Fl x .Op Fl csvgen | Fl c .Op Fl output Ar udp_name | Fl o Ar udp_name .Op Fl max Ar max_read | Fl m Ar max_read .Op Fl data .Op Fl a .Op Fl d Ar dest_ip Ns Op : Ns Ar port .Op Fl g | Fl good-ts-only .Op Fl keep-bad .Op Fl tfmt Ar time_format .Op Fl dump-data | Fl D .Op Fl extra-dump | Fl E .Op Fl times | Fl t .Op Fl skew-discontinuity-threshold Ar threshold | Fl skew Ar threshold .Ar file .Sh DESCRIPTION Report and/or extract the Transport Streams in a .pcap. In analyse mode ( .Fl a ) the timing info in the TS (PCRs) can be compared with the timing info in the pcap. .Bl -tag .It Fl h , help Produce usage summary .It Fl h Cm detail , Fl help Cm detail Produce usage + more detail on what the output actually means .It Fl a , analyse Analyse. Produces summary info on every TS in the pcap .It Fl d Ar dest_ip Ns Oo : Ns Ar port Oc , Fl destip Ar dest_ip Ns Oo : Ns Ar port Oc Select data with the given destination IP and port. If the .Ar port is not specified, it defaults to 0 (see below). .It Fl g , Fl good-ts-only Only extract/analyse packets that seem entirely good. By default there is a bit of slack in determining if a packet is good and some dodgy packets are let through. This switch ensures that all packets pass simple testing .It Fl keep-bad Extract all packets including bad ones. Is implied if an ip & port filter is set. Overriden by .Fl -good-ts-only . .It Fl tfmt Ar time_format Sets the format for printed times .Pp .Ar time_format is one of .Bl -tag .It Cm 90 .Bq Default show as 90KHz timestamps (suffix 't' on the values: e.g., 4362599t). .It Cm 27 Show as 27MHz timestamps (similar, e.g., 25151:000t). .It Cm 32 Show as 90KHz timestamps, but only the low 32 bits. .It Cm ms Show as milliseconds. .It Cm hms Show as hours/minutes/seconds (H:MM:SS.ssss, the H can be more than one digit if necessary) .El .It Fl dump-data , Fl D Dump any data in the input file to stdout. .It Fl extra-dump , Fl E Dump only data which isn't being sent to the Fl o file. .It Fl times , Fl t Report continuously on PCR vs PCAP timing for the destination specified in .Fl d . .It Fl skew-discontinuity-threshold Ar threshold , Fl skew Ar threshold Gives the skew discontinuity threshold in 90kHz units. A value of 0 disables this. .Bq "default = 6*90000" .It Fl split-section Split extracted streams into multiple files on section (discontinutity) boundries .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output extra information about packets .It Ar file The pcap stream file to get info on .El .Pp Specifying 0.0.0.0 for destination IP will capture all hosts, specifying 0 as a destination port will capture all ports on the destination host. .Pp Network packet numbers start at 1 (like wireshark) .Pp TS packet numbers start at 0. .Ss Analyse output .Bl -tag .It Times (packet and PCR) The times associated with packets and PCR are held internally in 90kHz units and are displayed in those units by default .It Stream A set of packets to the same IP & Port. TS streams are detected by looking for 0x47s at appropriate places in the packets .It Section A part of a stream which appears to have a continuous TS embedded in it. If the PCR jumps then a new section should be started (though this will not generate a separate .ts file if the extraction option is in effect unless .Fl split-section is specified, nor will it generate a new .csv file.) .Pp As it stands pcapreport will only report on a single PCR pid within a TS. If multiple pids with PCRs are detected then this will be reported but the other PCRs will be ignored .It Skew This is the difference between the time in the pcap for a UDP packet and any PCR found in the TS contained within that packet. The accuracy of this figure obviously depends on how good the clock was in the capture process. Skew is arbitrarily set to zero at the start of a section. A skew of >6s is assumed to be a discontinuity and will start a new section. .Pp Positive skew means that we received too low a PCR for this timestamp. .It Drift This is skew over time and (assuming that the playout process is good) represents the difference in speed between the transmitters clock and the receivers clock. The algorithm for determining this isn't very sophisticated so if you have a large maximum jitter or a short sample this should be taken with a pinch of salt. Beware also that PC clocks (like the one in the m/c doing the tcpdump) are not always amongst the most stable or accurate; however they should be good enough to detect gross errors .It Jitter This is measured as the difference between the maximum and minimum skews over a 10sec (max 1024 samples) period. This should be long enough to capture a good baseline but short enough that drift has a negligible effect .It Max Jitter The maximum value of jitter (see above) found in a section .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr tsinfo 1 , .Xr tsreport 1 .Xr rtp2264 1 .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .Sh BUGS pcapreport can only deal with IPv4. IPv6 is beyond its current capabilities. tstools-1.13~git20151030/docs/mdoc/ps2ts.1000066400000000000000000000137041261471605300177040ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt ps2ts 1 .Os .Sh NAME .Nm ps2ts .Nd Extract a program stream from a Transport Stream .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm ps2ts .Fl pid Ar pid | Fl video | audio .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl max Ar max_pkts | Fl m Ar max_pkts .Op Fl dvd | notdvd | nodvd .Op Fl vstream Ar vstream_no .Op Fl astream Ar astream_no .Op Fl ac3stream Ar ac3stream_no .Op Fl host Ar host Ns Op : Ns port .Op Fl vpid Ar vpid_no .Op Fl apid Ar apid_no .Op Fl noaudio .Op Fl pmt Ar pmt_pid_no .Op Fl prepeat Ar pat_freq .Op Fl pad Ar pad_pkts .Op Fl h264 | avc | h262 | mp42 | vtype Ar video_type .Op Fl dolby Cm dvd | atsc .Ar in_file | Fl stdin .Ar out_file | Fl stdout .Sh DESCRIPTION Convert an H.222 program stream to H.222 transport stream. .Pp This program does not make use of any Program Stream Map packets in the data (mainly because I have yet to see data with any). This means that the program has to determine the stream type of the data based on the first few ES units. .Pp This program does not output more than one video and one audio stream. If the program stream data contains more than one of each, the first will be used, and the others ignored (with a message indicating this). .Pp It is assumed that the video stream will contain DTS values in its PES packets at reasonable intervals, which can be used as PCR values in the transport stream, and thus the video stream's PID can be used as the PCR PID in the transport stream. .Ss Files .Bl -tag .It Ar in_file is a file containing the program stream data (but see -stdin below) .It Ar out_file is a transport stream file (but see -stdout and -host below) .El .Ss input switches .Bl -tag .It Fl stdin take input from , instead of a named file .It Fl dvd The PS data is from a DVD. This is the default. This switch has no effect on MPEG-1 PS data. .It Fl notdvd , nodvd The PS data is not from a DVD. The DVD specification stores AC-3 (Dolby), DTS and other audio in a specialised manner in private_stream_1. .It Fl vstream Ar vstream_no Take video from video stream .Ar vstream_no (0..7). The default is the first video stream found. .It Fl astream Ar astream_no Take audio from audio stream .Ar astream_no (0..31). The default is the first audio stream found (this includes private_stream_1 on non-DVD streams). .It Fl ac3stream Ar astream_no Take audio from AC3 substream .Ar ac3stream_no (0..7), from private_stream_1. This implies -dvd. (If audio is being taken from a substream, the user is assumed to have determined which one is wanted, e.g., using psreport) .El .Ss Output Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl host Ar host Ns Op : Ns Arport Writes output (over TCP/IP) to the named , instead of to a named file. If .Ar is not specified, it defaults to 88. .It Fl vpid Ar vpid_no .Ar vpid_no is the video PID to use for the data. Use '-vpid 0x' to specify a hex value. Defaults to 0x68. .It Fl apid Ar apid_no .Ar apid_no is the audio PID to use for the data. Use '-apid 0x' to specify a hex value. Defaults to 0x67. .It Fl noaudio Don't output the audio data .It Fl pmt Ar pmt_pid_no .Ar pmt_pid_no is the PMT PID to use. Use '-pmt 0x' to specify a hex value. Defaults to 0x66 .It Fl prepeat Ar pat_freq Output the program data (PAT/PMT) after every .Ar pat_freq PS packs. Defaults to 100. .It Fl pad Ar pad_pkts Pad the start with .Ar pad_pkts filler TS packets, to allow a TS reader to synchronize with the datastream. Defaults to 8. .El .Ss General switches .Bl -tag .It Fl v , Fl verbose Print a 'v' for each video packet and an 'a' for each audio packet, as it is read .It Fl q , Fl quiet Only output error messages .It Fl max Ar max_pkts , Fl m Ar max_pkts Maximum number of PS packets to read. .El .Ss Stream type When the TS data is being output, it is flagged to indicate whether it conforms to H.262, H.264, etc. It is important to get this right, as it will affect interpretation of the TS data. .Pp If input is from a file, then the program will look at the start of the file to determine if the stream is H.264 or H.262 data. This process may occasionally come to the wrong conclusion, in which case the user can override the choice using the following switches. .Pp If input is from standard input (via -stdin), then it is not possible for the program to make its own decision on the input stream type. Instead, it defaults to H.262, and relies on the user indicating if this is wrong. .Bl -tag .It Fl h264 , avc Force the program to treat the input as MPEG-4/AVC. .It Fl h262 Force the program to treat the input as MPEG-2. .It Fl mp42 Force the program to treat the input as MPEG-4/Part 2. .It Fl vtype Ar video_type Force the program to treat the input as video of stream type (e.g., 0x42 means AVS video). It is up to the user to specify a valid . .El If the audio stream being output is Dolby (AC-3), then the stream type used to output it differs for DVB (European) and ATSC (USA) data. It may be specified as follows: .Bl -tag .It Fl dolby Cm dvb Use stream type 0x06 (the default) .It Fl dolby Cm atsc Use stream type 0x81 .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/psdots.1000066400000000000000000000032371261471605300201450ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt PSDOTS 1 .Os .Sh NAME .Nm psdots .Nd Present the content of an PS as a sequence of characters .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm psdots .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl max Ar max_pkts | Fl m Ar max_pkts .Ar in_file | Fl stdin .Sh DESCRIPTION Present the content of a Program Stream file as a sequence of characters, representing the packets. .Ss Files .Bl -tag .It Ar in_file is an H.222 Program Stream file (but see .Fl stdin Ns ) .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl stdin Input from standard input, instead of a file .It Fl v , Fl verbose Output a description of the characters used .It Fl max Ar max_pkts , Fl m Ar max_pkts Maximum number of PS packets to read .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/psreport.1000066400000000000000000000036431261471605300205100ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt PSREPORT 1 .Os .Sh NAME .Nm psreport .Nd Report on the contents of a PS .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm psreport .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl max Ar max_pkts | Fl m Ar max_pkts .Op Fl dvd | notdvd | nodvd .Ar in_file | Fl stdin .Sh DESCRIPTION Report on the packets in a Program Stream. .Ss Files .Bl -tag .It Ar in_file is an H.222 Program Stream file (but see .Fl stdin below) .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl stdin Input from standard input, instead of a file .It Fl v , Fl verbose Output packet data as well .It Fl stdin Take input from , instead of a named file .It Fl max Ar max_pkts , Fl m Ar max_pkts Maximum number of PS packets to read .It Fl dvd The PS data is from a DVD. This is the default. This switch has no effect on MPEG-1 PS data. .It Fl notdvd , nodvd The PS data is not from a DVD. The DVD specification stores AC-3 (Dolby), DTS and other audio in a specialised manner in private_stream_1. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/rtp2264.1000066400000000000000000000026621261471605300177550ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt RTP2264 1 .Os .Sh NAME .Nm rtp2264 .Nd convert H.264 in RTP into H.264 ES .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm rtp2264 .Ar in_file .Ar out_file .Op Ar b64_block Ns Op , Ns Ar b64_block Ns Op ,... .Sh DESCRIPTION Take a RTP file (probably generated by .Xr pcapreport 1 Ns ) containing an H.264 stream and convert it into an Annex B encoded .264 elementary stream file. .Pp If .Ar b64_block Ns s are specified then it is assumed to be one or more B64 encoded blocks containing SPS or PPS or other similar blocks. These will each have a 00 00 00 01 sequence added at the start and then written at the start of the out_file .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr pcapreport 1 .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/stream_type.1000066400000000000000000000050161261471605300211620ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt STREAM_TYPE 1 .Os .Sh NAME .Nm stream-type .Nd Guess the type of a stream file .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm stream-type .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Ar in_file .Sh DESCRIPTION Attempt to determine if an input stream is Transport Stream, Program Stream, or Elementary Stream, and if the latter, if it is H.262 or H.264 (i.e., MPEG-2 or MPEG-4/AVC respectively). The mechanisms used are fairly crude, assuming that: .Bl -dash .It data is byte aligned .It for TS, the first byte in the file will be the start of a NAL unit, and PAT/PMT packets will be findable .It for PS, the first packet starts immediately at the start of the file, and is a pack header .It if the first 1000 packets could be H.262 *or* H.264, then the data is assumed to be H.264 (the program doesn't try to determine sensible sequences of H.262/H.264 packets, so this is a reasonable way of guessing) .El It is quite possible that data which is not relevant will be misidentified .Ss Files .Bl -tag .It Ar in_file is the file to analyse .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output more detailed information about how it is making its decision .It Fl q , Fl quiet Only output error messages .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .Sh RETURN VALUES The program exit value is: .Bl -tag .It 10 if it detects Transport Stream, .It 11 if it detects Program Stream, .It 12 if it detects Elementary Stream containing H.262 (MPEG-2), .It 14 if it detects Elementary Stream containing H.264 (MPEG-4/AVC), .It 5 if it looks like it might be PES, .It 9 if it really cannot decide, or .It 0 if some error occurred .El .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/ts2es.1000066400000000000000000000045761261471605300177000ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt TS2ES 1 .Os .Sh NAME .Nm ts2es .Nd Extract a program stream from a Transport Stream .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm ts2es .Fl pid Ar pid | Fl video | audio .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl max Ar max_pkts | Fl m Ar max_pkts .Op Fl pes | ps .Ar in_file | Fl stdin .Ar out_file | Fl stdout .Sh DESCRIPTION Extract a single (elementary) program stream from a Transport Stream (or Program Stream). .Ss Files .Bl -tag .It Ar in_file is an H.222 Transport Stream file (but see -stdin and -pes) .It Ar out_file is a single elementary stream file (but see -stdout) .El .Ss Which stream to extract: .Bl -tag .It Fl pid Ar pid Output data for the stream with the given .Ar pid . Use .Fl pid No 0x Ns Ar pid No to specify a hex value .It Fl video Output data for the (first) video stream named in the (first) PMT. This is the default. .It Fl audio Output data for the (first) audio stream named in the (first) PMT .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl stdin Input from standard input, instead of a file .It Fl v , Fl verbose Output extra information about packets .It Fl q , Fl quiet Only output error messages .It Fl max Ar max_pkts , Fl m Ar max_pkts Maximum number of TS packets to read. .It Fl pes , ps Use the PES interface to read ES units from the input file. This allows PS data to be read (there is no point in using this for TS data). Does not support .Fl pid , stdin No or Fl stdout. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/ts_packet_insert.1000066400000000000000000000040111261471605300221610ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt TS_PACKET_INSERT 1 .Os .Sh NAME .Nm ts_packet_insert .Nd Insert TS packets into a Transport Stream .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm ts_packet_insert .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl p Ar positions .Op Fl pid Ar pid_no .Op Fl s string .Op Fl o Ar out_file .Ar in_file .Sh DESCRIPTION Insert TS packets into a Transport Stream at positions specified by the user. .Ss Input .Bl -tag .It Ar in_file An H.222 Transport Stream file. .El .Ss Switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl p Ar positions This a a colon (':') delimited string of numbers between 0 and 1, representing how far through to put each TS packet. E.g., -p 0.1:0.4:0.7:0.9 will insert 4 packets at 10%, 40%, 70% and 90% through the file. .It Fl pid Ar pid_no The inserted packets will have the PID specfied. .Bq default = 0x68 .It Fl s Ar string The inserted packets will contain .Ar string as their payload. .Bq default = 'Inserted packet' .It Fl o Ar out_file The new TS file will be written out with the given name .Bq default = out.ts .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .Sh EXAMPLES ts_packet_insert -p 0.3:0.6 -o out.ts -pid 89 -s "AD=start" in.ts .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/tsdvbsub.1000066400000000000000000000040331261471605300204600ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt TSDVBSUB 1 .Os .Sh NAME .Nm tsdvbsub .Nd Dump DVB subtitling from a stream .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm tsdvbsub .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | q .Op Fl max Ar max_pkts | Fl m Ar max_pkts .Op Fl pid Ar pid_no .Op Fl prog Ar prog_no .Ar in_file | Fl stdin .Sh DESCRIPTION Parse & dump the contents of a single DVB subtitling stream from a Transport Stream (or Program Stream). .Ss Files .Bl -tag .It Ar in_file is an H.222 Transport Stream file (but see .Fl stdin and .Fl pes below). .El .Ss Stream to extract .Bl -tag .It Fl pid Ar pid_no Output data for the stream with the given .Ar pid_no . Use -pid 0x to specify a hex value .Bq default = the stream will be located from the PMT info .It Fl prog Ar prog_no Program number .Bq default = 1 .El .Ss general switches .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output informational/diagnostic messages .It Fl q , Fl quiet Only output error messages .It Fl stdin Input from standard input, instead of a file .It Fl m Ar max_pkts , Fl max Ar max_pkts Maximum number of TS packets to read .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/tsfilter.1000066400000000000000000000032411261471605300204600ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt TSFILTER 1 .Os .Sh NAME .Nm tsfilter .Nd Output a filtered or truncated version of a transport stream .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm tsfilter .Op Fl verbose | Fl v .Op Fl i Ar in_file .Op Fl o Ar out_file .Op Fl max Ar max_pkts | Fl m Ar max_pkts .Op Fl \&! | Fl invert .Ar pid_no Oo Ar pid_no Oc No ... .Sh DESCRIPTION Filter the given .Ar pid_no Ns s out of stdin and write the result on stdout. .Bl -tag .It Fl i Ar in_file Take input from this file and not stdin. .It Fl o Ar out_file Send output to this file and not stdout. .It Fl v , verbose Be verbose. .It Fl m Ar max_pkts, Fl max Ar max_pkts All packets after the nth are regarded as not matching any pids. .It Fl \&! , invert Invert whatever your decision was before applying it - the output contains only pids not in the list up to max packets and all packets in the input from then on. .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr esdots 1 , .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/tsinfo.1000066400000000000000000000036211261471605300201300ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt TSINFO 1 .Os .Sh NAME .Nm tsinfo .Nd get info about the contents of a transport stream .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm tsinfo .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl stdin .Op Fl verbose | Fl v .Op Fl max Ar max_scan | Fl m Ar max_scan .Op Fl repeat Ar PMT_count .Op Ar file .Sh DESCRIPTION Report on the program streams in a Transport Stream. This command just dumps the initial PAT/PMT pairing. If you want more info on the program streams within the transport stream then use .Xr tsreport 1 . .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl stdin Input from standard input, instead of a file .It Fl v , Fl verbose Output extra information about packets .It Fl m Ar max_scan , Fl max Ar max_scan .Ar max_qscan is the Number of TS packets to scan. Defaults to 10000. .It Fl repeat Ar PMT_count Look for .Ar PMT_count PMT packets, and report on each .It Ar file The transport stream file to get info on. If .Fl stdin is specified then no .Ar file is expected .El .\" The following commands should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr tsreport 1 .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/tsplay.1000066400000000000000000000056011261471605300201420ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt TSPLAY 1 .Os .Sh NAME .Nm tsplay .Nd stream a file .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm tsplay .Fl help .Op Ar subject .Nm tsplay .Op Fl details .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl quiet | q .Op Fl verbose | v .Op Fl loop .Op Fl max Ar max_pkts | Fl m Ar max_pkts .Op Fl mcastif Ar mcast_if | Fl i Ar mcast_if .Op Fl tcp | udp .Ar in_file | Fl stdin .Ar host Ns Oo : Ns Ar port Oc | .Fl output Ar out_file | Fl o Ar out_file | Fl stdout .Sh DESCRIPTION Act as a server which plays the given file (containing Transport Stream or Program Stream data). The output is always Transport Stream. .Ss Input .Bl -tag .It Ar in_file Input is from the named H.222 TS file. .It Fl stdin Input is from standard input. .El .Ss Output .Bl -tag .It Ar host Ns Op : Ns Ar port Normally, output is to a named host. If .Ar port is not specified, it defaults to 88. Output defaults to UDP. .It Fl o Ar out_file , Fl output Ar out_file Output is to file .Ar out_file . .It Fl tcp Output to the host is via TCP. .It Fl udp Output to the host is via UDP. .Bq default .It Fl stdout Output is to standard output. Forces .Fl quiet No and Fl "err stderr" . .It Fl i Ar mcast_if , Fl mcastif Ar mcast_if If output is via UDP, and .Ar host is a multicast address, then .Ar mcast_if is the IP address of the network interface to use. This may not be supported on some versions of Windows. .El .Ss General Switches .Bl -tag .It Fl details Print out more detailed help information, including some less common options. .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output additional diagnostic messages .It Fl q , Fl quiet Suppress informational and warning messages .It Fl help Summarise the .Ar subject Ns s that can be specified .It Fl help Ar subject Show help on a particular subject .It Fl m Ar max_pkts , Fl max Ar max_pkts Maximum number of TS/PS packets to read. See -details for more information. .It Fl loop Play the input file repeatedly. Can be combined with .Fl max . .El .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr tsinfo 1 , .Xr pcapreport 1 .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/tsreport.1000066400000000000000000000070621261471605300205130ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt TSREPORT 1 .Os .Sh NAME .Nm tsreport .Nd get info about the streams within a transport stream. .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm tsinfo .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl timing | Fl t .Op Fl max Ar max_read | Fl m Ar max_read .Op Fl data .Ar file | Fl stdin .Nm tsinfo .Fl buffering | Fl b .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl verbose | Fl v .Op Fl quiet | Fl q .Op Fl max Ar max_read | Fl m Ar max_read .Op Fl o Ar csv_file Op Fl 32 .Op Fl cnt .Op Fl prog Ar prog_no .Op Fl tfmt Ar time_format .Op Fl tafmt Ar time_format .Ar file | Fl stdin .Nm tsinfo .Fl justpid Ar pid .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl max Ar max_read | Fl m Ar max_read .Ar file | Fl stdin .Sh DESCRIPTION Report on the streams in a Transport Stream. In general the most useful inforation is returned by the .Fl b option. .Ss Common options .Bl -tag .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output extra information about packets .It Fl q , Fl quiet Output less information .It Fl m Ar max_scan , Fl max Ar max_scan .Ar max_scan is the Number of TS packets to scan. Defaults to the entire file. .It Fl stdin Input from standard input, instead of a file .It Ar file The transport stream file to get info on. If .Fl stdin is specified then no .Ar file is expected .El .Ss Fl b , Fl buffering Report on the differences between PCR and PTS, and between PCR and DTS. This is relevant to the size of buffers needed in the decoder. Also reports bitrates; the max bitrate is calculated over 0.5sec .Bl -tag .It Fl o Ar csv_file Op Fl 32 Output timing in to a CSV file called .Ar csv_file . If .Fl 32 is used as well then the timing ifo is restricted to the bottom 32 bits .It Fl cnt Ar pid Check values of continuity_counter for pid .Ar pid . Writes all the values of the counter to a file called .Pa continuity_counter.txt . .It Fl prog Ar prog_no Report on program prog_no .Bq "default = 1" (hopefully default will be 'all' in the future) .It Fl tfmt Ar time_format Specify format of time differences. .It Fl tafmt Ar time_format Specify format of absolute times. .Pp .Ar time_format is one of .Bl -tag .It Cm 90 .Bq Default show as 90KHz timestamps (suffix 't' on the values: e.g., 4362599t). .It Cm 27 Show as 27MHz timestamps (similar, e.g., 25151:000t). .It Cm 32 Show as 90KHz timestamps, but only the low 32 bits. .It Cm ms Show as milliseconds. .It Cm hms Show as hours/minutes/seconds (H:MM:SS.ssss, the H can be more than one digit if necessary) .El .El .Ss Fl justpid Ar pid Just show data (file offset, index, adaptation field and payload) for TS packets with the given PID. PID 0 is allowed (i.e., the PAT) .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr tsinfo 1 , .Xr pcapreport 1 .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/mdoc/tsserve.1000066400000000000000000000062621261471605300203250ustar00rootroot00000000000000.\" The following commands are required for all man pages. .Dd October 28, 2015 .Dt TSSERVE 1 .Os .Sh NAME .Nm tsserve .Nd get info about the streams within a transport stream .\" This next command is for sections 2 and 3 only. .\" .Sh LIBRARY .Sh SYNOPSIS .Nm tsserve .Op Fl details .Op Fl "err stdout" .Op Fl "err stderr" .Op Fl quiet | q .Op Fl verbose | v .Op Fl port Ar port_no .Op Fl noaudio .Op Fl pad Ar filler_pkts .Op Fl noseqhdr .Op Fl prepeat Ar pat_freq .Op Fl h264 | avc | h262 .Op Fl dolby Cm dvb | atsc .Op Fl 0 .Ar file0 .Op Fl 1 Ar file1 .Op Fl 2 Ar file2 .Ns ... .Sh DESCRIPTION Act as a server which plays the given file (containing Transport Stream or Program Stream data). The output is always Transport Stream. .Ss Input: .Bl -tag .It Ar infile An H.222.0 TS or PS file to serve to the client. This will be treated as file 0 (see below). .It Fl 0 Ar file0 No .. Fl 9 Ar file9 Specify files 0 through 9, selectable with command characters 0 through 9. The lowest numbered file will be the default for display. .El .Ss General Switches: .Bl -tag .It Fl details Print out more detailed help information, including some less common options. .It Fl "err stdout" Write error messages to standard output (the default) .It Fl "err stderr" Write error messages to standard error (Unix traditional) .It Fl v , Fl verbose Output additional diagnostic messages .It Fl q , Fl quiet Suppress informational and warning messages .It Fl port Ar port_no Listen for a client on port .Ar port_no .Bq default = 88 .It Fl noaudio Ignore any audio data .It Fl pad Ar filler_pkts Pad the start of the output with .Ar filler_pkts filler TS packets, to allow the client to synchronize with the datastream. .Bq default = 8 .It Fl noseqhdr Do not output sequence headers for fast forward/reverse data. Only relevant to H.262 data. .El .Ss Program Stream Switches: .Bl -tag .It Fl prepeat Ar pat_freq Output the program data (PAT/PMT) after every .Ar pat_freq PS packs. .Bq default = 100 .It Fl h264 , avc Force the program to treat the input as MPEG-4/AVC. .It Fl h262 Force the program to treat the input as MPEG-2. .El Both of these affect the stream type of the output data. .Pp If the audio stream being output is Dolby (AC-3), then the stream type used to output it differs for DVB (European) and ATSC (USA) data. It may be specified as follows: .Bl -tag .It Fl dolby Cm dvb Use stream type 0x06. .Bq default .It Fl dolby Cm atsc Use stream type 0x81 .El .Pp For information on using the program in other modes, see .Fl details. .\" The following cnds should be uncommented and .\" used where appropriate. .\" .Sh IMPLEMENTATION NOTES .\" This next command is for sections 2, 3 and 9 function .\" return values only. .\" .Sh RETURN VALUES .\" This next command is for sections 1, 6, 7 and 8 only. .\" .Sh ENVIRONMENT .\" .Sh FILES .\" .Sh EXAMPLES .\" This next command is for sections 1, 6, 7, 8 and 9 only .\" (command return values (to shell) and .\" fprintf/stderr type diagnostics). .\" .Sh DIAGNOSTICS .\" .Sh COMPATIBILITY .\" This next command is for sections 2, 3 and 9 error .\" and signal handling only. .\" .Sh ERRORS .Sh SEE ALSO .Xr tsinfo 1 , .Xr pcapreport 1 .\" .Sh STANDARDS .\" .Sh HISTORY .\" .Sh AUTHORS .\" .Sh BUGS tstools-1.13~git20151030/docs/todo.txt000066400000000000000000000224131261471605300173300ustar00rootroot00000000000000===== To do ===== .. notes: Check that all outstanding items in this file are still outstanding. Bugs: * esreverse (in particular, but really all the filtering programs) should not abort if an error occurs reading through the file - in esreverse's case, this causes no output to be produced, which is irritating. * When reading PS data, if the next packet does not start with 00 00 01, then a search will be made for the next pack header (specifically, this is done by ``read_PS_packet_start``). This means that a file with "broken" data in its middle can be coped with by the PS reader - it will skip when it fails to find the next 00 00 01 in the right place. It does not mean that other layers above the PS reader will cope with broken data neatly, in particular if said "breakage" happened inside the last PS packet read before the skip. Ideally, these other levels would also have a strategy for moving on to the next believed-good point (perhaps even the next PS pack header). NB: TS data is expected not to contain errors in this sense. Outstanding items: * Sort out filter for H.264 so that it works sensibly. Generally check that H.264 works correctly with tsserve - there are believed to be known transition issues still outstanding. In more detail: 1. When stopping filtering, go back in the reverse list to the previous IDR and output that before continuing. This is needed to stop things referring to non-existent reference frames. Note that this might be a good thing to do cosmetically when stripping as well, but since tsserve outputs all reference pictures when stripping, it is not *necessary*. 2. When reversing, an IDR must also be output, so that any P/B frames thereafter are known not to be referring "past" it. This can be done either by insisting that reversing stop on an IDR, or else by reading forwards and outputting I and IDR frames until an IDR is found. This adds a requirement to be able to identify IDR frames in the reverse list. * Worry about AFD values and sequence headers in H.262. *Believed DONE, except for the "read and remember the GOP" bits, which may or may not matter.* Note that if sequence headers are not output when F or FF is used (the default, currently), then using F or FF as soon as tsserve is listening will not display any pictures, until N is selected (at least on norton), because nothing is displayed before a sequence header. The work needed to make reversing work "properly" for H.262. For H.264 it is sufficient to output just the block of data for a picture when reversing. However, for H.262 it is clearly not that simple, if only because of the AFD problem. What is probably wanted for H.262 is something more like: * Remember the start of a frame * Remember the start of "relevant" sequence headers * Remember (as now) which sequence header that "corresponds" to a frame * Remember the AFD (it's only 8 bytes, and not all of that necessarily is needed) for each frame * Maybe also remember the start of GOP headers, and their correspondences (NB: remember to cope with data that doesn't have AFDs?) To *output* a frame then means: a. [Locate its GOP and output that] b. Locate its sequence header, and output that c. Move to the start of the frame, and read and output its picture header d. Output the AFD e. Read and output the rest of the frame Simple optimisations along the lines of "if the GOP/sequence header have not changed then don't bother to re-output them" and "if no sequence header has been output then don't output an AFD" can be added when the approach seems to be working. Refreshing my memory - the sequence of H.262 data is:: for 1..n: Seq header Seq extension for 0..n: Extension & user data for 1..n: optional: GOP header for 0..n: User data Picture header Picture coding extension for 0..n: Extension & user data # including AFD Picture data... Seq end MPEG-1 is slightly simpler, and goes something like:: for 1..n: Seq header for 0..n: Extension & user data for 1..n: optional: GOP header for 0..n: User data Picture header for 0..n: Extension & user data # including AFD Picture data... Seq end * PS reading may still be slow. * Continue to use valgrind and its cohorts to check for leaks and inefficiencies. ---------------------------------------- Other outstanding items, in no particular order. Some of these may never actually be worth the time to do. * Overhaul the older code to bring it up to the style of tsserve. * Locate and check all ``@@@`` comments in the code. * Consider moving the verbose and quiet flags into the various context entities, allowing closer control on exactly *what* is quiet/verbose. This should allow replacement of debug conditions in individual files with equivalent flags in the appropriate context, and closer control of what output is available for debugging. * Consider leaving a gap between PAT and PMT, since some clients read the program stream and program metadata in parallel, so may take a while to "notice" that they should be using a new PMT. This actually sounds like a good idea. * Maybe remove the (tail) recursion from write_some_TS_PES_packet (except that it doesn't appear to impact efficiency at all, so it can wait). * When outputting a picture (access unit), consider outputting the whole thing as one (TS) PES packet, rather than outputting each item/NAL unit as a PES packet. * Regularise/make sensible the units used for -max in all utilities (some are still working on NAL units/H.262 items when they should be using pictures?). * Make verbose and quiet be in the same order in all functions using them(!) * Write more documentation. * Note which functions are (only used as) LIBRARY (functions), and which "truly" EXTERN. * Check all help texts. In particular, check what tsplay does for its timing info, and ensure its help text is correct. Also, check what ps2ts does re timing, vs what it says in its help text. * PS from DVD has an extra field (navigation info?). A user has said that DVDAuthor won't write PS to DVD without it containing said field. Consider a small utility to read PS and write PS with said field (as a dummy) added in. * Report __LINE__ and __FILE__ in internal errors that "should not happen"??? * Regularise meaning and behaviour of -verbose, -quiet and -x (should that last be called thus, or something more like -debug?). Regularise if -verbose implies not -quiet, and vice versa, in particular. * Make all the messages output that currently say "picture" or "access unit" that *should* say "frame" actually say "frame". * In pes.c, should it allow such flexibility in choice of which PIDs to aggregate (or just keep) PES data for? If the user had to select video/audio PIDs up-front, and no other PES packets were kept, then it could get away with reusing the PES packet data for each (just resetting the innards, and reallocing the data buffer if it was not big enough). Would this save enough time to be worthwhile? * Similarly, the current codebase is profligate in its use of malloc/free, because of the way it handles all the "context" entities. It would be possible to use more local variables for many of these (although still at the cost of remembering to call setup/teardown functions on them). Given this, some variables could (probably) be reused instead of building new instances and then freeing them. Again, would this save enough time to be worthwhile? * Missing programs? - ts2ps -- a preliminary implementation exists (build it explicitly with ``make ts2ps``), but it generates incorrect data. - es2ps -- is this ever needed? - ps2es -- ``ts2es -pes`` should actually do this - tsdots -- would be useful sometimes .. ***** BEGIN LICENSE BLOCK ***** License ------- Version: MPL 1.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is the MPEG TS, PS and ES tools. The Initial Developer of the Original Code is Amino Communications Ltd. Portions created by the Initial Developer are Copyright |copy| 2008 the Initial Developer. All Rights Reserved. .. |copy| unicode:: 0xA9 .. copyright sign Contributor(s): Amino Communications Ltd, Swavesey, Cambridge UK .. ***** END LICENSE BLOCK ***** .. ------------------------------------------------------------------------------- .. vim: set filetype=rst expandtab shiftwidth=2: tstools-1.13~git20151030/docs/tools.txt000066400000000000000000001105451261471605300175270ustar00rootroot00000000000000======== TS Tools ======== .. contents:: Overview ======== The following tools are provided: :es2ts_: Read ES (video), output TS :esdots_: Print one character per ES unit :esfilter_: "Fast forward" ES video data to a file (outputs ES or TS) :esmerge_: Merge H.264 video and AAC ADTS audio ES to TS (very specific) :esreport_: Report on the contents of an ES file :esreverse_: "Reverse" ES video data to a file (outputs ES or TS) :ps2es: Use ts2es_ (``ts2es -pes``) to obtain the effect of this. :ps2ts_: Read PS data, output TS :psdots_: Print one character per PS packet :psreport_: Report on the contents of a PS file :stream_type_: Make a (barely) educated guess what a file contains :ts2es_: Extract an ES stream from a TS file :tsinfo_: Report program info for a TS file (summarise PAT/PMT info) :tsplay_: Play (and possibly loop) a PS/TS file over UDP (using timing info) or TCP :tsreport_: Report on the contents of a TS file :tsserve_: Serve PS/TS files to clients (multicast) over TCP There are also some test programs, which are not otherwise discussed: :test_es_unit_list: Test the working of ES unit lists :test_nal_unit_list: Test the working of NAL unit lists (built on the above) :test_pes: Test the working of PES reading It is the intention that all the tools should work on Linux, Windows, Mac OS/X and BSD, although not all variants will always be tested at all times. Common syntax and assumptions ============================= In all of the tools, the switches and filenames (when used) may be mixed at will - specifically, switches are allowed after filenames. This makes it more convenient to run a particular program more than once by repeating the previous command but appending new switches. All switches are indicated by a single introductory ``-``. No provision is made for coping with filenames that start with ``-``. All tools have the following command line options in common: :``-h``, ``-help``, ``--help``: Provide help text about the program. There may also be more detailed help texts for some programs, available via different commands, which this help text will explain. Note that all of the tools will provide this help text if they are run with no arguments. :``-q``, ``-quiet``: Output no text except error messages. :``-v``, ``-verbose``: Output extra text. What this is depends on the type of application; it might be extra information (for a report tool), or debugging information (for a processing tool). Note that verbose and quiet are [#]_ mutually incompatible. That is, there are only three states: 1. "normal" (not verbose, not quiet) 2. verbose (and not quiet) 3. quiet (and not verbose) .. [#] or should be - some programs might not yet enforce this. Several tools share the following switches: :``-tsout``: Output TS instead of ES (common amongst ES processing tools). :``-host ``, |hostandport|: Specify a host to output to, and optionally a port number. The default port number is 88. .. We can't put a colon in a field lists :field name:, so use an indirection... .. |hostandport| replace:: ``-host :`` :``-output ``, ``-o ``: Specify output to the named file. :``-pid ``: Specify a PID. This is read as decimal by default, but a hexadecimal value can be specifed as (for instance) ``-pid 0x68``. The specific switch ``-pid`` is not common, but variants are. :``-stdin``, ``-stdout``: Take input from the standard input, write output to the standard output. If -stdout is used, -quiet is always enforced. If input is from standard input, the tool will not be able to "guess" the input type -- tools will generally default to H.262 (MPEG-2) in this case. :``-h262``: Indicates that the input (video) data is H.262 data (actually, either MPEG-1 or MPEG-2). Stops the tool trying to determine this for itself. :``-h264``, ``-avc``: Indicates that the input (video) data is H.264 (i.e., MPEG-4/AVC). Stops the tools trying to determine this for itself. :``-dvd``, ``-notdvd``, ``-nodvd``: Indicates that the PS data being read conforms (or doesn't) to DVD conventions. This matters for audio data in private_stream_1, which is packaged as "substreams" in DVD data. The programs ps2ts and psreport assume DVD data (since program stream is normally obtained from DVDs). :``-dolby dvb``, ``-dolby atsc``: Dolby (AC-3) audio data may be written out using either of two TS stream types, 0x06 for DVB data, and 0x81 for ATSC data. When reading TS data, the stream type read is used for output, but when reading PS data and outputting TS data, a decision must be made. The default is to use the DVB convention. This switch is provided for ps2ts, tsplay and tsserve. :``-max ``: Some of the tools can stop after reading/processing of the appropriate data items. This can (for instance) be used to truncate data files, or to inspect only part of a data stream. :``-pes``: Some of the ES reading tools will instead read from TS or PS data if given the ``-pes`` switch. This saves piping data through ts2es or (for PS) ps2ts and ts2es. For all of the tools, the documentation provided by ``-help`` should be used to find current command line definitions - these are not necessarily repeated below. Error and warning messages ========================== Conventionally, error and warning messages are all output to ``stderr``. Error messages are prefixed by ``###``, and warning messages by ``!!!``. Errors may be expected to cause a tool to exit with "failure" status. The final (outermost) message in a sequence of error messages for a particular tool will indicate the tool name (this can be useful when piping several tools together). Common considerations ===================== Support for MPEG-1 ------------------ MPEG-1 is essentially a subset of MPEG-2. The tools provided do not explicitly support MPEG-1, but should be lax enough in their requirements for MPEG-2 to allow MPEG-1 data as well (for instance, not *requiring* the presence of Sequence Extensions). In general, if this document talks about MPEG-2 (or H.262), MPEG-1 may be assumed as well. Determining the file type ------------------------- Several of the tools attempt to determine what type of data is contained in the input file. The mechanism used opts for simplicity over rigour, and can thus conceivably make a mistake. In this case, the user should use the appropriate switch (e.g., ``-h262`` or ``-h264``) to override it. H.264 profiles -------------- The underlying H.264 (MPEG-4/AVC) library checks the flags in the first sequence parameter set to determine what H.264 profile the data claims to be following. This library supports the "main" profile (profile indicator 77), or other profiles if they declare that they only contain data from the "main" profile. If the first sequence parameter set indicates that the bistream contains a different protocol, and does not indicate that it is conforming to the "main" profile, then the library will output a warning message - for instance:: Warning: This bitstream declares itself as extended profile (88). This software does not support extended profile, and may give incorrect results or fail. It will continue regardless, however, as experience shows that this information is not always presented correctly. (Note that although this is a warning message it is not prefixed by ``!!!``. It is, however, still output to standard error.) Frames and fields ----------------- If the input data is being treated in terms of frames, the tools will all aggregate individual fields into "frames" (by appending the NAL units or ES units of the second field to the first). In MPEG-2 processing tools, sequence headers are (broadly) treated as pictures (and thus as frames), and are thus included in the frame count. In MPEG-4/AVC data, the way that access units are aggregated can (when an end of stream unit is found) lead to an apparent "extra" access unit at the end of the data. Broken Program Stream files --------------------------- `Determining the file type`_ above explained how the tools are tolerant of PS files that do not start with a pack header. When reading PS data, if the start of a packet is being read (i.e., bytes 0x00 0x00 0x01 0x*XX*, where *XX* is the packet's stream type) and some other sequence of bytes is found, then the software will scan forwards for the next pack header. If no next pack header is found, then it will exit with an error. This should cope with files that have "dropped" data, where some bytes have been lost. It is unlikely to help directly with files that have corrupted data, which will cause errors at higher levels (for instance, in the H.262 "picture" building routines). es2ts ===== Converts an elementary video stream to transport stream. For instance:: $ es2ts hp-trail.264 hp-trail.ts $ es2ts hp-trail.264 -host norton or, taking the newly created TS file from above, extracting its video stream using ts2es_ and outputting it anew with video in PID 0x99:: $ ts2es -video -stdout hp-trail.ts | es2ts -stdin -pid 0x99 hp-trail99.ts The input stream may be MPEG-1 (as a subset of MPEG-2), MPEG-2 or MPEG-4/AVC. By default the tool looks at the start of the input file to determine whether it is MPEG-2 or MPEG-4/AVC. Input may be a file or standard input. Output may be a file, standard output or a host via TCP. The maximum number of ES units to process may be specified with the ``-max`` switch. No decoding of the contents of ES units is made, so the only reason that the tool needs to know whether the data is MPEG-2 or MPEG-4/AVC is so that it can write appropriate TS. esdots ====== This is a diagnostic tool used to gain a "view" of the contents of a data file. For instance:: $ esdots -v hp-trail.264 Reading from hp-trail.264 Input appears to be MPEG-4/AVC (H.264) Warning: This bitstream declares itself as extended profile (88). This software does not support extended profile, and may give incorrect results or fail. Ipppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp pppppppppppppppppppppppppppppippppppppppppppppppppppppppppppppppppppppp ... lines omitted ... pppppppppppppppippppppppppppppppppppppppppppppppppppppppppppppp Found 2550 NAL units in 2548 access units 0 IDR, 5 I, 78 P, 0 B access units GOP size (s): max=1.1200, min=0.0400, mean=0.67682 $ esdots /data/CharliesAngels.es -max 1000 Reading from /data/CharliesAngels.es Input appears to be MPEG-2 (H.262) [E>iEUUbEUbEUpEUbEUbEUpEUbEUbEUpEUbEUbEU[E>iEUUbEUbEUpEUbEUbEUpEUbEUbEU ... lines omitted ... pEUbEUbEU[E>iEUUbEU Found 1000 MPEG2 items 30 I, 60 P, 98 B GOP times (s): max=0.4800, min=0.1200, mean=0.448125 If the ``-v`` switch is used, then an explanation of the meaning of the characters output will be prepended (it is slightly different depending on whether the input is MPEG-2 or MPEG-4/AVC). If the ``-gop`` switch is used, then each GOP duration is displayed. For H.264, where the GOP is not defined, we retrieve this data by measuring the time between two random access points. It looks like this:: [E>iEbEbEpEbEbEpEbEbEpEbEbE: 0.4800s [E>iEbEbE: 0.1200s [E>iEbEbEpEbEbEpEbEbEpEbEbE: 0.4800s ... The gop times are computed supposing thatthe frame rate is 25 fps. If this is not the case, the value can be changed using the -fr switch (e.g. "-fr 30"). esfilter ======== Reads an input video stream and outputs it to another file, possibly filtering the data. For instance:: $ esfilter -copy some-file.es shorter-copy.es -max 2000 just copies the first 2,000 ES units from one file to the other, :: $ esfilter -strip some-file.es stripped-file.es outputs just the I (and for H.264, IDR) frames, whilst:: $ esfilter -filter some-file.es filtered-file.es -max 2000 attempts to "fast forward" the input file, outputting the result, but stopping after 2,000 frames. The default "speed-up" is 8x, which may be altered with the ``-freq`` switch. Input is from an elementary stream, either an explicit file or standard input. Output can be to elementary stream (the default), or to transport stream (with the ``-tsout`` switch). For either, standard output can be chosen instead of an explicit file. If TS output is specified, writing to a host over TCP/IP can also be requested. Note that for ``-strip`` and ``-filter``, the ``-max`` switch acts on the number of frames, but since ``-copy`` works at the ES item/NAL unit level, its ``-max`` works on these lower level entities. Fast forward algorithms ----------------------- The simpler "strip" algorithm, acts by simply discarding all frames that are not I (or, for MPEG-4/AVC, IDR) frames. This is quick, and produces a result guaranteed to display without artefacts (since I and IDR frames do not refer outside themselves), but will produce a variable speed-up depending on the distribution of the different sorts of frame within the data. If the ``-allref`` switch is supplied as well, then the "strip" algorithm is modified to keep all reference frames in MPEG-4/AVC, and all I and P frames in MPEG-2. This will produce a lesser speed-up, which should still display safely. The more complex "filter" algorithm attempts to emulate a specific speed-up, defaulting to 8x (and alterable with the ``-freq`` switch). It may repeat frames to produce the requested speed. In either case, the output frames are not altered in any way, which means that the resultant data stream is unlikely to be technically valid. For instance, in the MPEG-4/AVC case, no attempt is made to amend frame numbers. Also, metadata may not be correct, since (in MPEG-4/AVC) only the first sequence and picture parameter sets found are output. esmerge ======= This utility was written specifically to merge an MPEG-4/AVC video stream with an AAC ADTS audio stream. For instance:: $ esmerge video.264 audio.aac result.ts It is also possible to specify the audio rate (which defaults to 44.1KHz). Timing information is output (based on 25 video frames/second and the given audio rate, assuming 1024 samples per frame) as follows: 1. In the PES PTS and TS PCR for the first NAL unit of every I or IDR video frame. The PCR is generated by using the PTS as its base and 0 as its extension. 2. In the TS PCR for the first NAL unit of every other video frame. 3. In the PES PTS and TS PCR for the first NAL unit of every audio frame. Whilst very specific at the moment, this tool could obviously be expanded to be more versatile if future needs require. Since original writing, some support for AVS (video) and MPEG layer 2 (audio) has been added. esreport ======== Reads an input elementary stream (or, with ``-pes``, PS or TS) and reports on it. For instance, for MPEG-2:: $ esreport -max 5 CharliesAngels.es Reading from CharliesAngels.es Input appears to be MPEG-2 (H.262) 00000000/0000: MPEG2 item b3 (SEQUENCE HEADER) size 140 00000140/0000: MPEG2 item b5 (Extension start) size 10 00000150/0000: MPEG2 item b8 (Group start) size 8 00000158/0000: MPEG2 item 00 (Picture) 1 (I) size 8 00000166/0000: MPEG2 item b5 (Extension start) size 9 Found 5 MPEG-2 items Where the format is: start_pos_in_file/start_pos_in_packet: MPEG2 item unit_start_code (explanation of unit_start_code and additional info if it is a picture) data_length Or, for AVC:: $ esreport -max 5 hp-trail.264 Reading from hp-trail.264 Input appears to be MPEG-4/AVC (H.264) 00000001/0000: NAL unit 3/7 (seq param set) 11: 67 58 00 15 96 53 01 68 24 88... Warning: This bitstream declares itself as extended profile (88). This software does not support extended profile, and may give incorrect results or fail. 00000015/0000: NAL unit 3/8 (pic param set) 5: 68 ce 38 80 00 00000023/0000: NAL unit 3/5 (IDR) 3191: 65 88 80 40 02 13 14 00 04 2f... 00003217/0000: NAL unit 2/1 (non-IDR) 5453: 41 9a 02 05 84 01 c5 d4 7d 88... 00008673/0000: NAL unit 2/1 (non-IDR) 7558: 41 9a 04 09 41 00 71 1a f8 ff... Stopping because 5 NAL units have been read Found 5 NAL units nal_ref_idc: 2 of 2 3 of 3 nal_unit_type: 2 of 1 (non-IDR) 1 of 5 (IDR) 1 of 7 (seq param set) 1 of 8 (pic param set) slice_type: 2 of 5 (All P) 1 of 7 (All I) Where the format is: start_pos_in_file/start_pos_in_packet: NAL unit nal_ref_idc/nal_unit_type (explanation of nal_unit_type) data_length(in bytes):first_data_bytes Or, at frame level:: $ esreport -frames -max 5 CharliesAngels.es Reading from CharliesAngels.es Input appears to be MPEG-2 (H.262) Sequence header: frames and fields I Frame #2 B Frame #0 B Frame #1 P Frame #5 Found 5 MPEG-2 pictures The number after the ``#`` is the frames temporal reference, and the "picture" count includes the sequence header. If ``-q`` is specified, only the final counts are output. The ``-x`` switch shows details of each NAL unit as it is read. esreverse ========= Reads an input video stream and outputs it to another file, in "fast reverse". For instance:: $ esreverse hp-trail.264 hp-reverse.264 Output may optionally be to Transport Stream, instead of ES:: $ esreverse hp-trail.264 -tsout hp-reverse.ts TS or PS data may be read (instead of ES) using the ``-pes`` switch:: $ esreverse -pes CVBt_hp_trail.ts -tsout CVBt_reversed.ts Note that this will only read (and reverse) the video stream. The "frequency" of frames to try to keep when reversing the data (thus the "speedup" in reversing) may be specified with the ``-freq`` switch. It defaults to 8. Reverse algorithms ------------------ The input data is scanned forwards. For MPEG-2, the location and index of I frames and sequence headers is remembered. For MPEG-4/AVC, the location and index of I and IDR frames is remembered. Reversing is then done by starting at the end of the array of remembered data, and moving backwards, attempting to reproduce the requsted frequency. This may entail repeating particular frames. In MPEG-2 data, each I frame "remembers" the sequence header that precedes it, and also the AFD that is applicable to itself. When outputting the reverse data, a section header is output if appropriate (i.e., if it not the same as for the "previous" I frame), but AFDs are output for each frame. In MPEG-4/AVC data, sequence parameter set and picture parameter set NAL units must be output at the start of the output. ``esreverse`` writes out the data for the last sequence and picture parameters sets found with each id (typically, this means sequence parameter set 0 and picture parameter set 0) at the start of the reversed output. ps2ts ===== Reads an input Program Stream and outputs equivalent Transport Stream. For instance:: $ ps2ts CharliesAngels.mpg CharliesAngels.ts One video stream is supported. If multiple audio streams are found in the PS, then the first will be output (unless ``-noaudio`` is used to suppress it). The video, audio and PMT PIDs may be specified. The video data will be output to TS with stream type 0x02 for MPEG-2 video, and 0x1b for MPEG-4/AVC video. An attempt is made to work out an appropriate stream type for the audio, depending upon what it is. Note that AC3 on DVD is stored in substreams, which must be "unpacked" when outputting the data as TS. PS program stream map and program stream directory are ignored, mainly because I have not yet seen data with these present. When writing the video data, the SCR base and extension from the PS pack header are used as the PCR base and extension. .. Note:: Reading PS data may be slower than reading ES or TS data. psdots ====== This is a diagnostic tool used to gain a "view" of the contents of a data file. For instance:: $ psdots -v CharliesAngels.mpg -max 100 Reading from CharliesAngels.mpg Stopping after 100 PS packets [Hv[v[v[v[a[a[v[v[v[v[v[v[a[v[v[v[v[v[v[v[v[v[v[a[a[v[v[v[v[v[v[v[v[v[v [v[v[a[a[v[v[v[v[v[v[v[v[v[v[v[a[v[v[v[v[v[v[v[v[v[v[v[v[a[a[v[v[v[v[v[ v[v[v[v[v[v[v[v[v[v[v[a[a[v[v[v[v[v[v[v[v[v[v[v[v[a[v[v[v[v Stopping after 100 packs If the ``-v`` switch is used, then an explanation of the meaning of the characters output will be prepended. psreport ======== Reports on the content of a Program Stream. For instance:: $ psreport CharliesAngels.mpg Reading from CharliesAngels.mpg Packets (total): 984111 Packs: 492055 Video packets (stream 0): 426945 min size 3748, max size 7178, mean size 7042.7 Audio packets (stream 0): 65110 min size 602, max size 5888, mean size 3556.2 Program stream maps: 0 Program stream directories: 0 Information about the packets can be obtained with the ``-v`` switch:: $ psreport CharliesAngels.mpg -v -max 3 Reading from CharliesAngels.mpg Stopping after 3 PS packets 00000000: Pack header: SCR 0 (0/0) mux rate 18020 00000014: System header 1 Read 1 system header 00000032: PS Packet 3 stream E0 (Video stream 0x0) Packet (7168 bytes): 00 00 01 e0 1b fa 8f c0 0a 21 00 07 6d c5 11 00 07 19 65 00... 00007200: Pack header: SCR 214800 (716/0) mux rate 18020 Read 0 system headers 00007214: PS Packet 5 stream E0 (Video stream 0x0) Packet (7168 bytes): 00 00 01 e0 1b fa 8b 00 01 ff eb 12 6b 21 0a f5 86 04 a4 eb... a00014382: Pack header: SCR 429600 (1432/0) mux rate 18020 Read 0 system headers 00014396: PS Packet 7 stream E0 (Video stream 0x0) Packet (7168 bytes): 00 00 01 e0 1b fa 8b 00 01 ff 25 7f b2 10 8d 75 bb 00 6b c9... Stopping after 3 packs Packets (total): 8 Packs: 3 Video packets (stream 0): 3 min size 7168, max size 7168, mean size 7168.0 Program stream maps: 0 Program stream directories: 0 stream_type =========== Looks at the start of a file to attempt to determine if it is: * Transport Stream * Program Stream * Elementary Stream containing MPEG-2 * Elementary Stream containing MPEG-4/AVC * PES The mechanisms used to decide are not very sophisticated, but appear to work in practice. For instance:: $ stream_type CharliesAngels.mpg Reading from CharliesAngels.mpg It appears to be Program Stream or, with "explanations" enabled:: $ stream_type CharliesAngels.mpg -v Reading from CharliesAngels.mpg Trying to read pack header File starts 00 00 01 BA - could be PS, reading pack header body OK, trying to read start of next packet Start of second packet found at right place - looks like PS It appears to be Program Stream ``stream_type`` returns an exit value which may be used in shell scripts to take action according to its decision. ts2es ===== Extract a single Elementary Stream from Transport Stream. For instance:: $ ts2es -video CharliesAngels.ts CharliesAngels.es The ES to be extracted may be the video stream (taken to be the first video stream found), the first audio stream found, or the stream with a specific PID. The ``-pes`` switch may be used to read data via the PES reading mechanisms, which allows ``ts2es`` to read PS data as well:: $ ts2es -pes CharliesAngels.mpg CharliesAngels.es tsinfo ====== Present information on the program streams within a TS file. For instance:: $ tsinfo CVBt_hp_trail.ts Reading from CVBt_hp_trail.ts Scanning 1000 TS packets Packet 9 is PAT Program list: Program 1 -> PID 0066 (102) Packet 10 is PMT with PID 0066 (102) Program streams: PID 0068 (104) -> Stream type 27 (H.264/14496-10 video (MPEG-4/AVC)) PID 0067 (103) -> Stream type 15 (13818-7 Audio with ADTS transport syntax) PCR PID 0068 (104) Found 1 PAT packet and 1 PMT packet in 1000 TS packets The tool looks through the first 1000 TS packets for PAT and PMT entries, and reports on their content. If multiple PAT/PMT entries are found, with differing content, then this will be reported:: $ tsinfo CharliesAngels.ts Reading from CharliesAngels.ts Scanning 1000 TS packets Packet 9 is PAT Program list: Program 1 -> PID 0066 (102) Packet 10 is PMT with PID 0066 (102) Program streams: PID 0068 (104) -> Stream type 2 (H.262/13818-2 video (MPEG-2) or 11172-2 constrained video) PCR PID 0068 (104) Packet 168 is PMT with PID 0066 (102) - content changed Program streams: PID 0068 (104) -> Stream type 2 (H.262/13818-2 video (MPEG-2) or 11172-2 constrained video) PID 0067 (103) -> Stream type 4 (13818-3 audio (MPEG-2)) PCR PID 0068 (104) Found 2 PAT packets and 2 PMT packets in 1000 TS packets tsplay ====== Plays TS data over UDP or TCP/IP. Multicasting is supported over UDP:: $ tsplay hp-trail.ts 255.1.1.1 Playing can be looped (i.e., to repeat indefinitely):: $ tsplay hp-trail.ts 255.1.1.1 -loop TCP/IP may also be used:: tsplay hp-trail.ts -tcp norton When playing over UDP, the tool manages a circular buffer of TS packets, which are output to the target host at appropriate times, based on the TS packet PCR. Best response will be obtained with a fast machine and locally stored data. Note that if output is over UDP, and output is to a multicast IP address, then the network interface to use for the multicast broadcast can be chosen with the ``-mcastif`` switch. For example:: tsplay hp-trail.ts 235.1.1.1:1234 -mcastif 192.168.172.12 This option may not be supported on some versions of Windows. There are many tuning options - see ``-help tuning`` for details. The most useful are probably: * ``-maxnowait`` -- This specifies how many packets (each of 7 TS packets) may be sent to the client without a gap. The default is 3, which is relatively conservative. If the output is choppy, and tsplay reports that it is having to restart its timing:: !!! Packet 701 (item 101): Outputting 0.5s late - restarting time sequence Maybe consider running with -maxnowait greater than 3 then increasing the ``-maxnowait`` value may help (a value of 10 is reasonable if the client process is happy with this). * ``-hd`` -- This is provided for playing HD video, and is a "short hand" instead of specifying a variety of other switches (including ``-maxnowait``) with suitable values. Circular buffer algorithm ------------------------- This is only used for output over UDP - it is not applicable to TCP/IP. Transport Stream packets are read in batches of 7 (chosen to give a sensible number of bytes for sending over the network - seven TS packets is 1316 bytes, which will fit within an ethernet packet of size 1500). These "batches" are written to a circular buffer. Each "batch" has a timestamp, derived from the PCRs in the Transport Stream. A child process is forked, which reads the "batch" entries from the circular buffer, and attempts to output over UDP at an appropriate time. If it believes that it is sending too fast, it will wait. If it believes that it is sending too slow, it will output multiple "batches" with no intervening gap, up to a maximum number (as mentioned above). A slow machine will find that it is always trying to catch up, and will not "feed" the UDP client adequately. Almost all aspects of the algorithm can be changed. Details are available via the ``-tuning`` switch. tsreport ======== Reports on the TS packets in a file. For instance:: $ tsreport CharliesAngels.ts Reading from CharliesAngels.ts !!! 156 bytes ignored at end of file - not enough to make a TS packet Read 382583 TS packets More information can be obtained with the ``-v`` switch:: $ tsreport CharliesAngels.ts -v -max 20 Reading from CharliesAngels.ts Stopping after 20 TS packets 00000000: TS Packet 1 PID 1fff PADDING - ignored 00000188: TS Packet 2 PID 1fff PADDING - ignored 00000376: TS Packet 3 PID 1fff PADDING - ignored 00000564: TS Packet 4 PID 1fff PADDING - ignored 00000752: TS Packet 5 PID 1fff PADDING - ignored 00000940: TS Packet 6 PID 1fff PADDING - ignored 00001128: TS Packet 7 PID 1fff PADDING - ignored 00001316: TS Packet 8 PID 1fff PADDING - ignored 00001504: TS Packet 9 PID 0000 [pusi] PAT section length: 00d (13) transport stream id: 0001 version number 00, current next 1, section number 0, last section number 0 Program 001 ( 1) -> PID 0066 (102) 00001692: TS Packet 10 PID 0066 [pusi] PMT section length: 012 (18) program number: 0001 version number 00, current next 1, section number 0, last section number 0 PCR PID: 0068 program info length: 0 Stream 02 (H.262/13818-2 video (MPEG-2) or 11172- -> PID 0068 Info (0 bytes) 00001880: TS Packet 11 PID 0068 [pusi] Adaptation field len 7 [flags 10]: PCR .. PCR 0 PES header Start code: 00 00 01 Stream ID: e0 (224) PES packet length: 1bfa (7162) Flags: 8f c0 PES-priority data-aligned copyright original/copy : PTS DTS PES header len 10 !!! Guard bits at start of PTS data are 2, not 3 PTS 112354 DTS 101554 00002068: TS Packet 12 PID 0068 00002256: TS Packet 13 PID 0068 00002444: TS Packet 14 PID 0068 00002632: TS Packet 15 PID 0068 00002820: TS Packet 16 PID 0068 00003008: TS Packet 17 PID 0068 00003196: TS Packet 18 PID 0068 00003384: TS Packet 19 PID 0068 00003572: TS Packet 20 PID 0068 Stopping after 20 packets Read 20 TS packets The ``-timing`` switch may be used to obtain PCR timing information:: $ tsreport CharliesAngels.ts -timing -max 500 Reading from CharliesAngels.ts Stopping after 500 TS packets .. PCR 0 .. PCR 214800 Mean byterate 921620 byterate 921620 .. PCR 429600 Mean byterate 921620 byterate 921620 .. PCR 644400 Mean byterate 921620 byterate 921620 .. PCR 5298300 Mean byterate 182986 byterate 80711 .. PCR 5513100 Mean byterate 211764 byterate 921620 .. PCR 5727900 Mean byterate 238384 byterate 921620 .. PCR 5942700 Mean byterate 263080 byterate 921620 .. PCR 6157500 Mean byterate 286053 byterate 921620 .. PCR 6372300 Mean byterate 307477 byterate 921620 .. PCR 10430700 Mean byterate 222394 byterate 88802 Stopping after 500 packets Read 500 TS packets The ``-cnt `` switch makes tsreport check the values of the ``continuity_counter`` field for the specified PID. It writes the values to a file called ``continuity_counter.txt``, in lines of the form:: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 which makes it easy to spot missing values. It also gives a warning for any discontinuities found, but note that the specification does allow the duplication of a TS packet once (which will lead to the continuity counter repeating once as well). Using ``-cnt`` automatically turns buffering on (-b). tsserve ======= Acts as a server, playing PS or TS files to clients. For instance:: $ tsserve CharliesAngels.mpg will listen for a client on the default port 88, and "serve" the file to it when a connection is made. Up to 10 files may be specified on the command line - for instance:: $ tsserve -0 CharliesAngels.mpg -1 hp-trail.ts -2 news24.ts specifies files 0, 1 and 2. The first example, where no file number is explicitly given, is equivalent to ``-0 CharliesAngels.mpg``. If port 88 is not suitable, an alternative may be chosen:: $ tsserve -port 8889 -0 CharliesAngels.mpg -1 hp-trail.ts -2 news24.ts The file being served starts in "p"ause mode. On reaching either end of the file (the end by playing forwards at normal or accelerated speed, or the start by rewinding), it returns to "p"ause mode. A client may send single character commands to the server: * ``p`` - pause (the initial state) * ``n`` - normal play * ``f`` - fast forward (using "strip" as described in `Fast forward algorithms`_). The speed of this is dependent on the distribution of reference frames in the data. * ``F`` - fast fast forward (using "filter" as described in `Fast forward algorithms`_). The speed of this is nominally 8x. * ``r`` - reverse (as described in `Reverse algorithms`_). The nominal speed for this is 8x. * ``R`` - reverse at double speed, i.e., 16x. * ``>`` - skip forwards 10 seconds. * ``]`` - skip forwards 3 minutes. * ``<`` - skip backwards 10 seconds. * ``[`` - skip backwards 3 minutes. * ``0`` .. ``9`` - select the file with that number, and start playing it again from the start. If there is no file with that number, then ignore the command. * ``q`` - quit this client. (typically by use of a handset). For each client, tsserver spawns a new server (on Unix with ``fork``, on Windows using a thread) which serves the nominated files to that client. No particular limit is specified for the number of clients allowed. When a client sends the ``q`` command, or if an error occurs, then the particular process for that client will be terminated. Notes ----- Each file is output as a different TS program, file 0 as program 1, file 1 as program 2, etc. Different PIDs will be used for the data in each program. For program *i*, * the video PID will be 0x68 + *i*, * the audio PID will be 0x68 + *i* + 10, and * the PMT PID will be 0x68 + *i* + 20. The PCR PID will be assumed to be the video PID. When reversing or fast forwarding MPEG-2 data, sequence headers will not, by default be output (see `Reverse algorithms`_ for some background on this). The ``-seqhdr`` switch may be used to override this. Alternate modes --------------- If only a specific host is to be used as a "client", then that host may be named explicitly:: $ tsserve -cmd -host norton -0 CharliesAngels.mpg -1 hp-trail.ts The ``-cmd`` indicates that the host may send single character commands (as above). It is also possible (on Unix, but not on Windows) to give commands to the program directly, instead of via the client socket. For instance:: $ tsserve -cmdstin -host norton -0 CharliesAngels.mpg -1 hp-trail.ts The program will then read commands from standard input. Note, however, that such commands will not be "seen" until a newline is typed (at which point any commands typed will occur in the order given). Some canned test modes are also supplied, which perform a reproducable sequence of actions. These are described in the ``-details`` help text. .. ***** BEGIN LICENSE BLOCK ***** License ======= Version: MPL 1.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is the MPEG TS, PS and ES tools. The Initial Developer of the Original Code is Amino Communications Ltd. Portions created by the Initial Developer are Copyright |copy| 2008 the Initial Developer. All Rights Reserved. .. |copy| unicode:: 0xA9 .. copyright sign Contributor(s): Amino Communications Ltd, Swavesey, Cambridge UK .. ***** END LICENSE BLOCK ***** .. ------------------------------------------------------------------------------- .. vim: set filetype=rst expandtab shiftwidth=2: tstools-1.13~git20151030/es.c000066400000000000000000001364031261471605300154520ustar00rootroot00000000000000/* * Utilities for reading H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "printing_fns.h" #include "misc_fns.h" #include "pes_fns.h" #include "tswrite_fns.h" #include "es_fns.h" #include "printing_fns.h" #define DEBUG 0 // A lone forwards reference static inline int get_more_data(ES_p es); // ------------------------------------------------------------ // Basic functions // ------------------------------------------------------------ /* * Open an ES file and build an elementary stream datastructure to read * it with. * * - `filename` is the ES files name. As a special case, if this is NULL * then standard input (STDIN_FILENO) will be read from. * * Opens the file for read, builds the datastructure, and reads the first 3 * bytes of the input file (this is done to prime the triple-byte search * mechanism). * * Returns 0 if all goes well, 1 otherwise. */ extern int open_elementary_stream(char *filename, ES_p *es) { int err; int input; if (filename == NULL) input = STDIN_FILENO; else { input = open_binary_file(filename,FALSE); if (input == -1) return 1; } err = build_elementary_stream_file(input,es); if (err) { fprint_err("### Error building elementary stream for file %s\n", filename); return 1; } return 0; } static int setup_readahead(ES_p es) { int err; es->read_ahead_len = 0; es->read_ahead_posn = 0; es->data = NULL; es->data_end = NULL; es->data_ptr = NULL; es->last_packet_posn = 0; es->last_packet_es_data_len = 0; // Try to get the first chunk of data from the file err = get_more_data(es); if (err) return err; if (es->reading_ES) { if (es->read_ahead_len < 3) { fprint_err("### File only contains %d byte%s\n", es->read_ahead_len,(es->read_ahead_len==1?"":"s")); return 1; } } else { if (es->reader->packet->es_data_len < 3) { fprint_err("### File PES packet only contains %d byte%s\n", es->reader->packet->es_data_len, (es->reader->packet->es_data_len==1?"":"s")); return 1; } } if (DEBUG) fprint_msg("File starts %02x %02x %02x\n",es->data[0],es->data[1],es->data[2]); // Despite (maybe) reporting the above, we haven't actually read anything // yet es->prev2_byte = es->prev1_byte = es->cur_byte = 0xFF; es->posn_of_next_byte.infile = 0; es->posn_of_next_byte.inpacket = 0; return 0; } /* * Build an elementary stream datastructure attached to an input file. * This is intended for reading ES data files. * * - `input` is the file stream to read from. * * Builds the datastructure, and reads the first 3 bytes of the input * file (this is done to prime the triple-byte search mechanism). * * Returns 0 if all goes well, 1 otherwise. */ extern int build_elementary_stream_file(int input, ES_p *es) { ES_p new = malloc(SIZEOF_ES); if (new == NULL) { print_err("### Unable to allocate elementary stream datastructure\n"); return 1; } new->reading_ES = TRUE; new->input = input; new->reader = NULL; setup_readahead(new); *es = new; return 0; } /* * Build an elementary stream datastructure for use with a PES reader. * Reads the first (or next) three bytes of the ES. * * This reads data from the PES video data, ignoring any audio data. * * - `reader` is the PES reader we want to use to read our TS or PS data. * * The caller must explicitly close the PES reader as well as closing the * elementary stream (closing the ES does not affect the PES reader). * * Returns 0 if all goes well, 1 otherwise. */ extern int build_elementary_stream_PES(PES_reader_p reader, ES_p *es) { ES_p new = malloc(SIZEOF_ES); if (new == NULL) { print_err("### Unable to allocate elementary stream datastructure\n"); return 1; } new->reading_ES = FALSE; new->input = -1; new->reader = reader; setup_readahead(new); *es = new; return 0; } /* * Tidy up the elementary stream datastructure after we've finished with it. * * Specifically: * * - free the datastructure * - set `es` to NULL * * No return status is given, since there's not much one can do if anything * *did* go wrong, and if something went wrong and the program is continuing, * it's bound to show up pretty soon. */ extern void free_elementary_stream(ES_p *es) { (*es)->input = -1; // "forget" our input free(*es); *es = NULL; } /* * Tidy up the elementary stream datastructure after we've finished with it. * * Specifically: * * - close the input file (if its stream is set, and if it's not STDIN) * - call `free_elementary_stream()` * * No return status is given, since there's not much one can do if anything * *did* go wrong, and if something went wrong and the program is continuing, * it's bound to show up pretty soon. */ extern void close_elementary_stream(ES_p *es) { int input; if (*es == NULL) return; input = (*es)->input; if (input != -1 && input != STDIN_FILENO) (void) close_file(input); free_elementary_stream(es); } /* * Ask an ES context if changed input is available. * * This is a convenience wrapper to save querying the ES context to see * if it is (a) reading from PES, (b) automatically writing the PES packets * out via a TS writer, and (c) if said TS writer has a changed command. * * Calls `tswrite_command_changed()` on the TS writer associated with this ES. * * Returns TRUE if there is a changed command. */ extern int es_command_changed(ES_p es) { if (es->reading_ES) return FALSE; if (es->reader->tswriter == NULL) return FALSE; return tswrite_command_changed(es->reader->tswriter); } // ------------------------------------------------------------ // Handling elementary stream data units // ------------------------------------------------------------ /* * Prepare the contents of a (new) ES unit datastructure. * * Allocates a new data array, and unsets the counts. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int setup_ES_unit(ES_unit_p unit) { unit->data = malloc(ES_UNIT_DATA_START_SIZE); if (unit->data == NULL) { print_err("### Unable to allocate ES unit data buffer\n"); return 1; } unit->data_len = 0; unit->data_size = ES_UNIT_DATA_START_SIZE; unit->start_posn.infile = 0; unit->start_posn.inpacket = 0; unit->PES_had_PTS = FALSE; // See the header file return 0; } /* * Tidy up an ES unit datastructure after we've finished with it. * * (Frees the internal data array, and unsets the counts) */ extern void clear_ES_unit(ES_unit_p unit) { if (unit->data != NULL) { free(unit->data); unit->data = NULL; unit->data_size = 0; unit->data_len = 0; } } /* * Build a new ES unit datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_ES_unit(ES_unit_p *unit) { int err; ES_unit_p new = malloc(SIZEOF_ES_UNIT); if (new == NULL) { print_err("### Unable to allocate ES unit datastructure\n"); return 1; } err = setup_ES_unit(new); if (err) { free(new); return 1; } *unit = new; return 0; } /* * Build a new ES unit datastructure, from a given data array. * * Takes a copy of 'data'. Sets 'start_code' appropriately, * sets 'start_posn' to (0,0), and 'PES_had_PTS' to FALSE. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_ES_unit_from_data(ES_unit_p *unit, byte *data, uint32_t data_len) { ES_unit_p new = malloc(SIZEOF_ES_UNIT); if (new == NULL) { print_err("### Unable to allocate ES unit datastructure\n"); return 1; } new->data = malloc(data_len); if (new->data == NULL) { print_err("### Unable to allocate ES unit data buffer\n"); return 1; } (void) memcpy(new->data, data, data_len); new->data_len = data_len; new->data_size = data_len; new->start_code = data[3]; new->start_posn.infile = 0; new->start_posn.inpacket = 0; new->PES_had_PTS = FALSE; // See the header file *unit = new; return 0; } /* * Tidy up and free an ES unit datastructure after we've finished with it. * * Empties the ES unit datastructure, frees it, and sets `unit` to NULL. * * If `unit` is already NULL, does nothing. */ extern void free_ES_unit(ES_unit_p *unit) { if (*unit == NULL) return; clear_ES_unit(*unit); free(*unit); *unit = NULL; } /* * Print out some information this ES unit, on normal or error output */ extern void report_ES_unit(int is_msg, ES_unit_p unit) { byte s = unit->start_code; fprint_msg_or_err(is_msg, OFFSET_T_FORMAT_08 "/%4d: ES unit (%02x '%d%d%d%d %d%d%d%d')", unit->start_posn.infile,unit->start_posn.inpacket,s, (s&0x80)>>7,(s&0x40)>>6,(s&0x20)>>5,(s&0x10)>>4, (s&0x08)>>3,(s&0x04)>>2,(s&0x02)>>1,(s&0x01)); // Show the data bytes - but we don't need to show the first 4, // since we know they're 00 00 01 if (unit->data_len > 0) { int ii; int data_len = unit->data_len - 4; int show_len = (data_len>10?10:data_len); fprint_msg_or_err(is_msg," %6d:",data_len); for (ii = 0; ii < show_len; ii++) fprint_msg_or_err(is_msg," %02x",unit->data[4+ii]); if (show_len < data_len) fprint_msg_or_err(is_msg,"..."); } fprint_msg_or_err(is_msg,"\n"); } // ------------------------------------------------------------ // ES unit *data* stuff // ------------------------------------------------------------ /* * A wrapper for `read_next_PES_ES_packet()`, to save us forgetting things * we need to do when we call it. * * Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise * 1 if some error occurs. */ static inline int get_next_pes_packet(ES_p es) { int err; PES_reader_p reader = es->reader; // Before reading the *next* packet, remember where the last one was if (reader->packet == NULL) { // What can we do if there was no last packet? es->last_packet_posn = 0; es->last_packet_es_data_len = 0; } else { es->last_packet_posn = reader->packet->posn; es->last_packet_es_data_len = reader->packet->es_data_len; } err = read_next_PES_ES_packet(es->reader); if (err) return err; // Point to our (new) data buffer es->data = reader->packet->es_data; es->data_end = es->data + reader->packet->es_data_len; es->data_ptr = es->data; es->posn_of_next_byte.infile = reader->packet->posn; es->posn_of_next_byte.inpacket = 0; return 0; } /* * Read some more data into our read-ahead buffer. For a "bare" file, * reads the next buffer-full in, and for PES based data, reads the * next PES packet that contains ES data. * * Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise * 1 if some error occurs. */ static inline int get_more_data(ES_p es) { if (es->reading_ES) { // Call `read` directly - we don't particularly mind if we get a "short" // read, since we'll just catch up later on #ifdef _WIN32 int len = _read(es->input,&es->read_ahead,ES_READ_AHEAD_SIZE); #else ssize_t len = read(es->input,&es->read_ahead,ES_READ_AHEAD_SIZE); #endif if (len == 0) return EOF; else if (len == -1) { fprint_err("### Error reading next bytes: %s\n",strerror(errno)); return 1; } es->read_ahead_posn += es->read_ahead_len; // length of the *last* buffer es->read_ahead_len = len; es->data = es->read_ahead; // should be done in the setup function es->data_end = es->data + len; // one beyond the last byte es->data_ptr = es->data; return 0; } else { return get_next_pes_packet(es); } } /* * Find the start of the next ES unit - i.e., a 00 00 01 start code prefix. * * Doesn't move the read position if we're already *at* the start of * an ES unit. * * ((new scheme: Leaves the data_ptr set to read the *next* byte, since * we know that we've "used up" the 00 00 01 at the start of this unit.)) * * Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise * 1 if some error occurs. */ static int find_ES_unit_start(ES_p es, ES_unit_p unit) { int err; byte prev1 = es->prev1_byte; byte prev2 = es->prev2_byte; // In almost all cases (hopefully, except for the very start of the file), // a previous call to find_ES_unit_end will already have positioned us // "over" the start of the next unit for (;;) { byte *ptr; for (ptr = es->data_ptr; ptr < es->data_end; ptr++) { if (prev2 == 0x00 && prev1 == 0x00 && *ptr == 0x01) { es->prev1_byte = es->prev2_byte = 0x00; es->cur_byte = 0x01; if (es->reading_ES) { unit->start_posn.infile = es->read_ahead_posn + (ptr - es->data) - 2; } else { unit->start_posn.infile = es->reader->packet->posn; unit->start_posn.inpacket = (ptr - es->data) - 2; if (unit->start_posn.inpacket < 0) { unit->start_posn.infile = es->last_packet_posn; unit->start_posn.inpacket += es->last_packet_es_data_len; } // Does the PES packet that we are starting in have a PTS? unit->PES_had_PTS = es->reader->packet->has_PTS; } es->data_ptr = ptr + 1; // the *next* byte to read unit->data[0] = 0x00; // i.e., the values we just read unit->data[1] = 0x00; unit->data[2] = 0x01; unit->data_len = 3; return 0; } prev2 = prev1; prev1 = *ptr; } // We've run out of data - get some more err = get_more_data(es); if (err) return err; } } /* * Find (read to) the end of the current ES unit. * * Reads to just before the next 00 00 01 start code prefix. * * H.264 rules would also allow us to read to just before a 00 00 00 * sequence, but we shall ignore this ability, because when reading * ES we want to ensure that there are no bytes "left out" of the ES * units, so that the sum of the lengths of all the ES units we read will * be the same as the length of data we have read. This is desirable * because it makes handling ES via PES much easier (we can, for instance, * position to the first ES unit of a picture, and then just read in N * bytes, where N is the sum of the lengths of the ES units making up the * picture, which is much more efficient than having to read in individual * ES units, and takes less room to remember than having to remember the * end position (offset of PES packet in file + offset of end of ES unit in * PES packet)). * * ((new scheme: Leaves the data_ptr set to read the current byte again, * since we know that, in general, we want to detect it in find_ES_unit_start * as the 01 following on from a 00 and a 00.)) * * Returns 0 if it succeeds, otherwise 1 if some error occurs. * * Note that finding end-of-file is not counted as an error - it is * assumed that it is just the natural end of the ES unit. */ static int find_ES_unit_end(ES_p es, ES_unit_p unit) { int err; byte prev1 = es->cur_byte; byte prev2 = es->prev1_byte; for (;;) { byte *ptr; for (ptr = es->data_ptr; ptr < es->data_end; ptr++) { // Have we reached the end of our unit? // We know we are if we've found the next 00 00 01 start code prefix. // (as stated in the header comment above, we're ignoring the H.264 // ability to end if we've found a 00 00 00 sequence) if (prev2 == 0x00 && prev1 == 0x00 && *ptr == 0x01) { es->data_ptr = ptr; // remember where we've got to es->prev2_byte = 0x00; // we know prev1_byte is already 0 es->cur_byte = 0x01; // We've read two 00 bytes we don't need into our data buffer... unit->data_len -= 2; if (es->reading_ES) { es->posn_of_next_byte.infile = es->read_ahead_posn + (ptr - es->data) - 2; } else { es->posn_of_next_byte.infile = es->reader->packet->posn; es->posn_of_next_byte.inpacket = (ptr - es->data) - 2; } return 0; } // Otherwise, it's a data byte if (unit->data_len == unit->data_size) { int newsize = unit->data_size + ES_UNIT_DATA_INCREMENT; unit->data = realloc(unit->data,newsize); if (unit->data == NULL) { print_err("### Unable to extend ES unit data array\n"); return 1; } unit->data_size = newsize; } unit->data[unit->data_len++] = *ptr; prev2 = prev1; prev1 = *ptr; } // We've run out of data (ptr == es->data_end) - get some more err = get_more_data(es); if (err == EOF) { // Reaching the end of file is a legitimate way of stopping! es->data_ptr = ptr; // remember where we've got to es->prev2_byte = prev2; es->prev1_byte = prev1; es->cur_byte = 0xFF; // the notional byte off the end of the file //es->cur_byte = *ptr; // Pretend there's a "next byte" if (es->reading_ES) { es->posn_of_next_byte.infile = es->read_ahead_posn + (ptr - es->data); } else { es->posn_of_next_byte.inpacket = (ptr - es->data); } return 0; } else if (err) return err; if (!es->reading_ES) { // If we update this now, it will be correct when we return, // even if we return because of a later EOF es->posn_of_next_byte.infile = es->reader->packet->posn; // Does the PES packet that we have just read in have a PTS? // If it does, then there's a very good chance (subject to a 00 00 01 // being split between PES packets) that our ES unit has a PTS "around" // it if (es->reader->packet->has_PTS) unit->PES_had_PTS = TRUE; } } } /* * Find and read in the next ES unit. * * In general, unless there are compelling reasons, use * `find_and_build_next_ES_unit()` instead. * * - `es` is the elementary stream we're reading from. * - `unit` is the datastructure into which to read the ES unit * - any previous content will be lost. * * Returns 0 if it succeeds, EOF if the end-of-file is read (i.e., there * is no next ES unit), otherwise 1 if some error occurs. */ extern int find_next_ES_unit(ES_p es, ES_unit_p unit) { int err; err = find_ES_unit_start(es,unit); if (err) return err; // 1 or EOF err = find_ES_unit_end(es,unit); if (err) return err; // The first byte after the 00 00 01 prefix tells us what sort of thing // we've found - we'll be friendly and extract it for the user unit->start_code = unit->data[3]; return 0; } /* * Find and read the next ES unit into a new datastructure. * * - `es` is the elementary stream we're reading from. * - `unit` is the datastructure containing the ES unit found, or NULL * if there was none. * * Returns 0 if it succeeds, EOF if the end-of-file is read (i.e., there * is no next ES unit), otherwise 1 if some error occurs. */ extern int find_and_build_next_ES_unit(ES_p es, ES_unit_p *unit) { int err; err = build_ES_unit(unit); if (err) return 1; err = find_next_ES_unit(es,*unit); if (err) { free_ES_unit(unit); return err; } return 0; } /* * Write (copy) the current ES unit to the output stream. * * Note that it writes out all of the data for this ES unit, * including its 00 00 01 start code prefix. * * - `output` is the output stream (file descriptor) to write to * - `unit` is the ES unit to write * * Returns 0 if all went well, 1 if something went wrong. */ extern int write_ES_unit(FILE *output, ES_unit_p unit) { size_t written = fwrite(unit->data,1,unit->data_len,output); if (written != unit->data_len) { fprint_err("### Error writing out ES unit data: %s\n" " Wrote %ld bytes instead of %d\n", strerror(errno),(long int)written,unit->data_len); return 1; } else return 0; } // ------------------------------------------------------------ // Arbitrary reading from ES data // ------------------------------------------------------------ /* * Seek within PES underlying ES. * * This should only be used to seek to data that starts with 00 00 01. * * "Unsets" the triple byte context. * * Returns 0 if all goes well, 1 if something goes wrong. */ static int seek_in_PES(ES_p es, ES_offset where) { int err; if (es->reader == NULL) { print_err("### Attempt to seek in PES for an ES reader that" " is not attached to a PES reader\n"); return 1; } // Force the reader to forget its current packet if (es->reader->packet != NULL) free_PES_packet_data(&es->reader->packet); // Seek to the right packet in the PES data err = set_PES_reader_position(es->reader,where.infile); if (err) { fprint_err("### Error seeking for PES packet at " OFFSET_T_FORMAT "\n",where.infile); return 1; } // Read the PES packet containing ES (ignoring packets we don't care about) err = get_next_pes_packet(es); if (err) { fprint_err("### Error reading PES packet at " OFFSET_T_FORMAT "/%d\n", where.infile,where.inpacket); return 1; } // Now sort out the byte offset if (where.inpacket > es->reader->packet->es_data_len) { fprint_err("### Error seeking PES packet at " OFFSET_T_FORMAT "/%d: " " packet ES data is only %d bytes long\n",where.infile, where.inpacket,es->reader->packet->es_data_len); return 1; } es->posn_of_next_byte = where; return 0; } /* * Update our current position information after a seek or direct read. */ static inline void deduce_correct_position(ES_p es) { // We don't know what the previous three bytes were, but we (strongly) // assume that they were not 00 00 01 es->cur_byte = 0xff; es->prev1_byte = 0xff; es->prev2_byte = 0xff; if (es->reading_ES) { // For ES data, we want to force new data to be read in from the file es->data_ptr = es->data_end = NULL; es->read_ahead_len = 0; // to stop the read ahead posn being incremented es->read_ahead_posn = es->posn_of_next_byte.infile; } else { // For PES data, we have whatever is left in the current packet PES_packet_data_p packet = es->reader->packet; es->data = packet->es_data; es->data_ptr = packet->es_data + es->posn_of_next_byte.inpacket; es->data_end = packet->es_data + packet->es_data_len; // And, of course, we have no idea about the *previous* packet in the file es->last_packet_posn = es->last_packet_es_data_len = 0; } } /* * "Seek" to the given position in the ES data, which is assumed to * be an offset ready to read a 00 00 01 sequence. * * If the ES reader is using PES to read its data, then both fields * of `where` are significant, but if the underlying file *is* just a file, * only `where.infile` is used. * * Returns 0 if all went well, 1 is something went wrong */ extern int seek_ES(ES_p es, ES_offset where) { int err; if (es->reading_ES) { err = seek_file(es->input,where.infile); if (err) { print_err("### Error seeking within ES file\n"); return 1; } } else { err = seek_in_PES(es,where); if (err) { fprint_err("### Error seeking within ES over PES (offset " OFFSET_T_FORMAT "/%d)\n",where.infile,where.inpacket); return 1; } } // And make it look as if we reached this position sensibly es->posn_of_next_byte = where; deduce_correct_position(es); return 0; } /* * Retrieve ES bytes from PES as requested * * Leaves the PES reader set to read on after this data. * * Returns 0 if all goes well, 1 if something goes wrong. */ static int read_bytes_from_PES(ES_p es, byte *data, uint32_t num_bytes) { int err; int offset = 0; int num_bytes_wanted = num_bytes; int32_t from = es->posn_of_next_byte.inpacket; int32_t num_bytes_left = es->reader->packet->es_data_len - from; for (;;) { if (num_bytes_left < num_bytes_wanted) { memcpy(&(data[offset]),&(es->reader->packet->es_data[from]), num_bytes_left); offset += num_bytes_left; num_bytes_wanted -= num_bytes_left; err = get_next_pes_packet(es); if (err) return err; from = 0; num_bytes_left = es->reader->packet->es_data_len; } else { memcpy(&(data[offset]),&(es->reader->packet->es_data[from]), num_bytes_wanted); from += num_bytes_wanted; break; } } es->posn_of_next_byte.inpacket = from; //es->posn_of_next_byte.infile = es->reader->packet->posn; return 0; } /* * Read in some ES data from disk. * * Suitable for use when reading in a set of ES units whose bounds * (start offset and total number of bytes) have been remembered. * * "Seeks" to the given position in the ES data, which is assumed to * be an offset ready to read a 00 00 01 sequence, and reads data thereafter. * * After this function, the triple byte context is set to FF FF FF, and the * position of said bytes are undefined, but the next position to read a byte * from *is* defined. * * The intent is to allow the caller to have a data array (`data`) that * always contains the last data read, and is of the required size, and * need only be freed when no more data is needed. * * - `es` is where to read our data from * - `start_posn` is the file offset to start reading at * - `num_bytes` is how many bytes we want to read * - `data_len` may be NULL or a pointer to a value. * If it is NULL, then the data array will be reallocated to size * `num_bytes` regardless. If it is non-NULL, it should be passed *in* * as the size that `data` *was*, and will be returned as the size * that `data` is when the function returns. * - `data` is the data array to read into. If this is NULL, or if `num_bytes` * is NULL, or if `num_bytes` is greater than `data_len`, then it will be * reallocated to size `num_bytes`. * * Returns 0 if all went well, 1 if something went wrong. */ extern int read_ES_data(ES_p es, ES_offset start_posn, uint32_t num_bytes, uint32_t *data_len, byte **data) { int err; if (*data == NULL || data_len == NULL || num_bytes > *data_len) { *data = realloc(*data,num_bytes); if (*data == NULL) { print_err("### Unable to reallocate data space\n"); return 1; } if (data_len != NULL) *data_len = num_bytes; } err = seek_ES(es,start_posn); if (err) return err; if (es->reading_ES) { err = read_bytes(es->input,num_bytes,*data); if (err) { if (err == EOF) { fprint_err("### Error (EOF) reading %d bytes\n",num_bytes); return 1; } else return err; } es->posn_of_next_byte.infile = start_posn.infile + num_bytes; } else { err = read_bytes_from_PES(es,*data,num_bytes); if (err) { fprint_err("### Error reading %d bytes from PES\n",num_bytes); return 1; } } // Make it look as if we "read" to this position by the normal means, // but ensure that we have no data left "in hand" // // We could leave it up to our caller to do this, on the assumption that // they're likely to call us several times when, for example, reversing, // without wanting to read onwards on all but the last of those occasions. // That would, indeed, save some time each time we are called, but it would // also allow our caller to forget to do this, with rather bad results. // // So, since it shouldn't really take very long... deduce_correct_position(es); return 0; } /* * Retrieve ES data from the end of a PES packet. It is assumed (i.e, things * will go wrong if it is not true) that at least one ES unit has been read * from the PES data stream via the ES reader. * * - `es` is our ES reader. It must be reading ES from PES packets. * - `data` is the ES data remaining (to be read) in the current PES packet. * It is up to the caller to free this data. * - `data_len` is the length of said data. If this is 0, then `data` * will be NULL. * * Returns 0 if all goes well, 1 if an error occurs. */ extern int get_end_of_underlying_PES_packet(ES_p es, byte **data, int *data_len) { int32_t offset; if (es->reading_ES) { fprint_err("### Cannot retrieve end of PES packet - the ES data" " is direct ES, not ES read from PES\n"); return 1; } if (es->reader->packet == NULL) { // This is naughty, but we'll pretend to cope *data = NULL; *data_len = 0; return 0; } // The offset (in this packet) of the next ES byte to read. // We assume that this must also be the offset of the first byte // of the next ES unit (or, at least, of one of the 00 bytes that // come before it). offset = es->posn_of_next_byte.inpacket; // The way that we read (using our "triple byte" mechanism) means that // we will generally already have read the start of the next ES unit. // Life gets interesting if the 00 00 01 triplet (or, possibly, 00 00 00 // triplet - but we're not supporting that option for the moment - see // find_ES_unit_end for details) is split over a PES packet boundary. // So we know we 00 00 01 to "start" a new ES unit and end the previous // one. (In fact, even if it was 00 00 00, the relevant values are held in // our triple byte memory, so we don't particularly care which it is.) // // If offset is 0, then then next byte to read is the first byte of // this packet's ES data, so we need to "pretend" to have all three // of the triple bytes "in front of" the actual ES data for this PES // packet. // // If offset is 1, then presumably the cur_byte was at offset 0, and // we have two "dangling" bytes in the previous packet. // // If offset is 2, then there would only be one "dangling" byte. // // Finally, if offset is 3 or more, we know there was room for the // 00 00 01 or 00 00 00 before the next byte we'll read, so we don't // need to bluff at all. // So, to calculation - we must remember to leave room for those // three bytes at the start of the data we return *data_len = es->reader->packet->es_data_len - offset + 3; *data = malloc(*data_len); if (*data == NULL) { print_err("### Cannot allocate space for rest of PES packet\n"); return 1; } (*data)[0] = es->prev2_byte; // Hmm - should be 0x00 (*data)[1] = es->prev1_byte; // Hmm - should be 0x00 (*data)[2] = es->cur_byte; // Hmm - should be 0x01 (see above) memcpy( &((*data)[3]), &(es->reader->packet->es_data[offset]), (*data_len) - 3); return 0; } // ------------------------------------------------------------ // Lists of ES units // ------------------------------------------------------------ /* * Build a new list-of-ES-units datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_ES_unit_list(ES_unit_list_p *list) { ES_unit_list_p new = malloc(SIZEOF_ES_UNIT_LIST); if (new == NULL) { print_err("### Unable to allocate ES unit list datastructure\n"); return 1; } new->length = 0; new->size = ES_UNIT_LIST_START_SIZE; new->array = malloc(SIZEOF_ES_UNIT*ES_UNIT_LIST_START_SIZE); if (new->array == NULL) { free(new); print_err("### Unable to allocate array in ES unit list datastructure\n"); return 1; } *list = new; return 0; } /* * Add a copy of an ES unit to the end of the ES unit list * * Note that since this takes a copy of the ES unit's data, it is safe * to free the original ES unit. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int append_to_ES_unit_list(ES_unit_list_p list, ES_unit_p unit) { ES_unit_p ptr; if (list->length == list->size) { int newsize = list->size + ES_UNIT_LIST_INCREMENT; list->array = realloc(list->array,newsize*SIZEOF_ES_UNIT); if (list->array == NULL) { print_err("### Unable to extend ES unit list array\n"); return 1; } list->size = newsize; } ptr = &list->array[list->length++]; // Some things can be copied directly *ptr = *unit; // But some need adjusting ptr->data = malloc(unit->data_len); if (ptr->data == NULL) { print_err("### Unable to copy ES unit data array\n"); return 1; } memcpy(ptr->data,unit->data,unit->data_len); ptr->data_size = unit->data_len; return 0; } /* * Tidy up an ES unit list datastructure after we've finished with it. */ static inline void clear_ES_unit_list(ES_unit_list_p list) { if (list->array != NULL) { int ii; for (ii=0; iilength; ii++) { clear_ES_unit(&list->array[ii]); } free(list->array); list->array = NULL; } list->length = 0; list->size = 0; } /* * Reset (empty) an ES unit list. */ extern void reset_ES_unit_list(ES_unit_list_p list) { if (list->array != NULL) { int ii; for (ii=0; iilength; ii++) { clear_ES_unit(&list->array[ii]); } // We *could* also shrink it - as it is, it will never get smaller // than its maximum size. Is that likely to be a problem? } list->length = 0; } /* * Tidy up and free an ES unit list datastructure after we've finished with it. * * Clears the datastructure, frees it and returns `list` as NULL. * * Does nothing if `list` is already NULL. */ extern void free_ES_unit_list(ES_unit_list_p *list) { if (*list == NULL) return; clear_ES_unit_list(*list); free(*list); *list = NULL; } /* * Report on an ES unit list's contents. * * - `name` is the name of the list (used in the header) * - `list` is the list to report on */ extern void report_ES_unit_list(char *name, ES_unit_list_p list) { fprint_msg("ES unit list '%s': ",name); if (list->array == NULL) print_msg("\n"); else { int ii; fprint_msg("%d item%s (size %d)\n",list->length, (list->length==1?"":"s"),list->size); for (ii=0; iilength; ii++) { print_msg(" "); report_ES_unit(TRUE,&(list->array[ii])); } } } /* * Retrieve the bounds of this ES unit list in the file it was read from. * * - `list` is the ES unit list we're interested in * - `start` is its start position (i.e., the location at which to start * reading to retrieve all of the data for the list) * - `length` is the total length of the ES units within this list * * Returns 0 if all goes well, 1 if the ES unit list has no content. */ extern int get_ES_unit_list_bounds(ES_unit_list_p list, ES_offset *start, uint32_t *length) { int ii; if (list->array == NULL || list->length == 0) { print_err("### Cannot determine bounds of an ES unit list with no content\n"); return 1; } *start = list->array[0].start_posn; *length = 0; // Maybe we should precalculate, or even cache, the total length... for (ii=0; iilength; ii++) (*length) += list->array[ii].data_len; return 0; } /* * Compare two ES unit lists. The comparison does not include the start * position of the unit data, but just the actual data - i.e., two unit lists * read from different locations in the input stream may be considered the * same if their data content is identical. * * - `list1` and `list2` are the two ES unit lists to compare. * * Returns TRUE if the lists contain identical content, FALSE otherwise. */ extern int same_ES_unit_list(ES_unit_list_p list1, ES_unit_list_p list2) { int ii; if (list1 == list2) return TRUE; if (list1->array == NULL) return (list2->array == NULL); if (list1->length != list2->length) return FALSE; for (ii = 0; ii < list1->length; ii++) { ES_unit_p unit1 = &list1->array[ii]; ES_unit_p unit2 = &list2->array[ii]; if (unit1->data_len != unit2->data_len) return FALSE; if (memcmp(unit1->data,unit2->data,unit1->data_len)) return FALSE; } return TRUE; } /* * Compare two ES offsets * * Returns -1 if offset1 < offset2, 0 if they are the same, and 1 if * offset1 > offset2. */ extern int compare_ES_offsets(ES_offset offset1, ES_offset offset2) { if (offset1.infile < offset2.infile) return -1; else if (offset1.infile > offset2.infile) return 1; else if (offset1.inpacket < offset2.inpacket) return -1; else if (offset1.inpacket > offset2.inpacket) return 1; else return 0; } // ============================================================ // Simple file type guessing // ============================================================ /* * Is an ES unit H.262 or H.264 (or not sure?) * * Return 0 if all goes well, 1 if things go wrong. */ static int try_to_guess_video_type(ES_unit_p unit, int show_reasoning, int *maybe_h264, int *maybe_h262, int *maybe_avs) { byte nal_ref_idc = 0; byte nal_unit_type = 0; if (show_reasoning) fprint_msg("Looking at ES unit with start code %02X\n",unit->start_code); // The following are *not allowed* // // - In AVS: B4, B8 and B9..FF are system start codes // - In H.262: B0, B1, B6 and B9..FF are system start codes // - In H.264: Anything with top bit set if (unit->start_code == 0xBA) // PS pack header { print_err("### ES unit start code is 0xBA, which looks like a PS pack" " header\n i.e., data may be PS\n"); return 1; } if (unit->start_code >= 0xB9) // system start code - probably PES { fprint_err("### ES unit start code %02X is more than 0xB9, which is probably" " a PES system start code\n i.e., data may be PES, " "and is thus probably PS or TS\n", unit->start_code); return 1; } if (unit->start_code & 0x80) // top bit set means not H.264 { if (*maybe_h264) { if (show_reasoning) fprint_msg(" %02X has top bit set, so not H.264,\n",unit->start_code); *maybe_h264 = FALSE; } if (unit->start_code == 0xB0 || unit->start_code == 0xB1 || unit->start_code == 0xB6) { *maybe_h262 = FALSE; if (show_reasoning) fprint_msg(" Start code %02X is reserved in H.262, so not H.262\n", unit->start_code); } else if (unit->start_code == 0xB4 || unit->start_code == 0xB8) { *maybe_avs = FALSE; if (show_reasoning) fprint_msg(" Start code %02X is reserved in AVS, so not AVS\n", unit->start_code); } } else if (*maybe_h264) { if (show_reasoning) print_msg(" Top bit not set, so might be H.264\n"); // If we don't have that top bit set, then we need to work a bit harder nal_ref_idc = (unit->start_code & 0x60) >> 5; nal_unit_type = (unit->start_code & 0x1F); if (show_reasoning) fprint_msg(" Interpreting it as nal_ref_idc %d, nal_unit_type %d\n", nal_ref_idc,nal_unit_type); if (nal_unit_type > 12 && nal_unit_type < 24) { if (show_reasoning) fprint_msg(" H.264 reserves nal_unit_type %02X," " so not H.264\n",nal_unit_type); *maybe_h264 = FALSE; } else if (nal_unit_type > 23) { if (show_reasoning) fprint_msg(" H.264 does not specify nal_unit_type %02X," " so not H.264\n",nal_unit_type); *maybe_h264 = FALSE; } else if (nal_ref_idc == 0) { if (nal_unit_type == 5 || // IDR picture nal_unit_type == 7 || // sequence parameter set nal_unit_type == 8) // picture parameter set { if (show_reasoning) fprint_msg(" H.264 does not allow nal_ref_idc 0 and nal_unit_type %d," " so not H.264\n",nal_unit_type); *maybe_h264 = FALSE; } } else // nal_ref_idc is NOT 0 { // Which means it should *not* be: if (nal_unit_type == 6 || // SEI nal_unit_type == 9 || // access unit delimiter nal_unit_type == 10 || // end of sequence nal_unit_type == 11 || // end of stream nal_unit_type == 12) // fille { if (show_reasoning) fprint_msg(" H.264 insists nal_ref_idc shall be 0 for nal_unit_type %d," " so not H.264\n",nal_unit_type); *maybe_h264 = FALSE; } } } return 0; } /* * Look at the start of an elementary stream to try to determine its * video type. * * "Eats" the ES units that it looks at, and doesn't rewind the stream * afterwards. * * - `es` is the ES file * - if `print_dots` is true, print a dot for each ES unit that is inspected * - if `show_reasoning` is true, then output messages explaining how the * decision is being made * - `video_type` is the final decision -- one of VIDEO_H264, VIDEO_H262, * VIDEO_AVS, or VIDEO_UNKNOWN. * * Returns 0 if all goes well, 1 if something goes wrong */ extern int decide_ES_video_type(ES_p es, int print_dots, int show_reasoning, int *video_type) { int err; int ii; int maybe_h262 = TRUE; int maybe_h264 = TRUE; int maybe_avs = TRUE; int decided = FALSE; struct ES_unit unit; *video_type = VIDEO_UNKNOWN; err = setup_ES_unit(&unit); if (err) { print_err("### Error trying to setup ES unit before" " working out video type\n"); return 1; } // Otherwise, look at the first 500 packets to see if we can tell // // Basically, if we find anything with the top byte as B, then it is // not H.264. Since H.262 allows up to AF (175) slices (which start with // 01..AF), and AVS the same (or perhaps one more) and each "surrounds" those // with entities with top byte B, it's rather hard to see how we could go // very far without finding something with the top bit of the high byte set // (certainly not as far as 500 units). So if we *do* go that far, we can be // *very* sure it is not H.262 or AVS. And if the only other choice is H.264, // then... if (show_reasoning) print_msg("Looking through first 500 ES units to try to decide video type\n"); for (ii=0; ii<500; ii++) { if (print_dots) { print_msg("."); fflush(stdout); } else if (show_reasoning) fprint_msg("%d: ",ii+1); err = find_next_ES_unit(es,&unit); if (err == EOF) { if (print_dots) print_msg("\n"); if (show_reasoning) fprint_msg("End of file, trying to read ES unit %d\n",ii+2); break; } else if (err) { if (print_dots) print_msg("\n"); fprint_err("### Error trying to find 'unit' %d in ES whilst" " working out video type\n",ii+2); clear_ES_unit(&unit); return 1; } err = try_to_guess_video_type(&unit,show_reasoning, &maybe_h264,&maybe_h262,&maybe_avs); if (err) { if (print_dots) print_msg("\n"); print_err("### Whilst trying to work out video_type\n"); clear_ES_unit(&unit); return 1; } if (maybe_h264 && !maybe_h262 && !maybe_avs) { if (show_reasoning) print_msg(" Which leaves only H.264\n"); *video_type = VIDEO_H264; decided = TRUE; } else if (!maybe_h264 && maybe_h262 && !maybe_avs) { if (show_reasoning) print_msg(" Which leaves only H.262\n"); *video_type = VIDEO_H262; decided = TRUE; } else if (!maybe_h264 && !maybe_h262 && maybe_avs) { if (show_reasoning) print_msg(" Which leaves only AVS\n"); *video_type = VIDEO_AVS; decided = TRUE; } else { if (show_reasoning) print_msg(" It is not possible to decide from that start code\n"); } if (decided) break; } if (print_dots) print_msg("\n"); clear_ES_unit(&unit); return 0; } /* * Look at the start of an elementary stream to try to determine it's * video type. * * Note that it is easier to prove something is H.262 (or AVS) than to prove * that it is H.264, and that the result of this routine is a best-guess, not a * guarantee. * * Rewinds back to the original position in the file after it has finished. * * - `input` is the file to look at * - if `print_dots` is true, print a dot for each ES unit that is inspected * - if `show_reasoning` is true, then output messages explaining how the * decision is being made * - `video_type` is the final decision -- one of VIDEO_H264, VIDEO_H262, * VIDEO_AVS, or VIDEO_UNKNOWN. * * Returns 0 if all goes well, 1 if something goes wrong */ extern int decide_ES_file_video_type(int input, int print_dots, int show_reasoning, int *video_type) { offset_t start_posn; int err; ES_p es = NULL; start_posn = tell_file(input); if (start_posn == -1) { print_err("### Error remembering start position in file before" " working out video type\n"); return 1; } err = seek_file(input,0); if (err) { print_err("### Error rewinding file before working out video type\n"); return 1; } err = build_elementary_stream_file(input,&es); if (err) { print_err("### Error starting elementary stream before" " working out video type\n"); return 1; } err = decide_ES_video_type(es,print_dots,show_reasoning,video_type); if (err) { print_err("### Error deciding video type of file\n"); free_elementary_stream(&es); return 1; } free_elementary_stream(&es); err = seek_file(input,start_posn); if (err) { print_err("### Error returning to start position in file after" " working out video type\n"); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/es2ts.c000066400000000000000000000316101261471605300160750ustar00rootroot00000000000000/* * Convert an Elementary Stream to Transport Stream. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "es_fns.h" #include "ts_fns.h" #include "tswrite_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" /* * Write (copy) the current ES data unit to the output stream, wrapped up in a * PES within TS. * * Returns 0 if all went well, 1 if something went wrong. */ static int write_ES_unit_as_TS(TS_writer_p output, ES_unit_p unit, uint32_t video_pid) { int err = write_ES_as_TS_PES_packet(output,unit->data,unit->data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing ES data unit\n"); return err; } else return 0; } static int transfer_data(ES_p es, TS_writer_p output, uint32_t pmt_pid, uint32_t video_pid, byte stream_type, int max, int verbose, int quiet) { int err = 0; int count = 0; // Write out a PAT and PMT first, or our stream won't make sense if (!quiet) fprint_msg("Using transport stream id 1, PMT PID %#x, program 1 =" " PID %#x, stream type %#x\n",pmt_pid,video_pid, stream_type); err = write_TS_program_data(output,1,1,pmt_pid,video_pid,stream_type); if (err) { print_err("### Error writing out TS program data\n"); return 1; } for (;;) { ES_unit_p unit; err = find_and_build_next_ES_unit(es,&unit); if (err == EOF) break; else if (err) { print_err("### Error copying ES data units\n"); return err; } count++; if (verbose) report_ES_unit(FALSE,unit); err = write_ES_unit_as_TS(output,unit,video_pid); if (err) { free_ES_unit(&unit); print_err("### Error copying ES data units\n"); return err; } free_ES_unit(&unit); if (max > 0 && count >= max) break; } if (!quiet) fprint_msg("Transferred %d ES data unit%s\n",count,(count==1?"":"s")); return 0; } static void print_usage() { print_msg( "Usage: es2ts [switches] [] []\n" "\n" ); REPORT_VERSION("es2ts"); print_msg( "\n" " Convert an elementary video stream to H.222 transport stream.\n" " Supports input streams conforming to MPEG-2 (H.262), MPEG-4/AVC\n" " (H.264) and AVS. Also supports MPEG-1 input streams, insofar as MPEG-2\n" " is backwards compatible with MPEG-1.\n" "\n" " Note that this program works by reading and packaging the elementary\n" " stream packages directly - it does not parse them as H.262 or H.264\n" " data.\n" "\n" "Files:\n" " is a file containing the Elementary Stream data\n" " (but see -stdin below)\n" " is an H.222 Transport Stream file\n" " (but see -stdout and -host below)\n" "\n" "Switches:\n" " -pid is the video PID to use for the data.\n" " Use '-pid 0x' to specify a hex value.\n" " Defaults to 0x68.\n" " -pmt is the PMT PID to use.\n" " Use '-pmt 0x' to specify a hex value.\n" " Defaults to 0x66\n" " -verbose, -v Output summary information about each ES packet\n" " as it is read\n" " -quiet, -q Only output error messages\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Take input from , instead of a named file\n" " -stdout Write output to , instead of a named file\n" " Forces -quiet and -err stderr.\n" " -host , -host :\n" " Writes output (over TCP/IP) to the named ,\n" " instead of to a named file. If is not\n" " specified, it defaults to 88.\n" " -max , -m Maximum number of ES data units to read\n" "\n" "Stream type:\n" " When the TS data is being output, it is flagged to indicate whether\n" " it conforms to H.262, H.264 or AVS. It is important to get this right,\n" " as it will affect interpretation of the TS data.\n" "\n" " If input is from a file, then the program will look at the start of\n" " the file to determine if the stream is H.264, H.262 or AVS. This\n" " process may occasionally come to the wrong conclusion, in which case\n" " the user can override the choice using the following switches.\n" "\n" " If input is from standard input (via -stdin), then it is not possible\n" " for the program to make its own decision on the input stream type.\n" " Instead, it defaults to H.262, and relies on the user indicating if\n" " this is wrong.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" " -avs Force the program to treat the input as AVS.\n" ); } int main(int argc, char **argv) { int use_stdin = FALSE; int use_stdout = FALSE; int use_tcpip = FALSE; int port = 88; // Useful default port number char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; TS_writer_p output = NULL; ES_p es; int verbose = FALSE; int quiet = FALSE; int max = 0; uint32_t video_pid = 0x68; uint32_t pmt_pid = 0x66; int err = 0; int err2; int ii = 1; int video_type = VIDEO_H262; // hopefully a sensible default int force_stream_type = FALSE; byte stream_type = 0; // silly value to keep compiler quiet if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-help",argv[ii]) || !strcmp("-h",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-avc",argv[ii]) || !strcmp("-h264",argv[ii])) { force_stream_type = TRUE; video_type = VIDEO_H264; } else if (!strcmp("-h262",argv[ii])) { force_stream_type = TRUE; video_type = VIDEO_H262; } else if (!strcmp("-avs",argv[ii])) { force_stream_type = TRUE; video_type = VIDEO_AVS; } else if (!strcmp("-stdin",argv[ii])) { had_input_name = TRUE; // more or less use_stdin = TRUE; } else if (!strcmp("-stdout",argv[ii])) { had_output_name = TRUE; // more or less use_stdout = TRUE; redirect_output_stderr(); } else if (!strcmp("-err",argv[ii])) { CHECKARG("es2ts",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### es2ts: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-host",argv[ii])) { CHECKARG("es2ts",ii); err = host_value("es2ts",argv[ii],argv[ii+1],&output_name,&port); if (err) return 1; had_output_name = TRUE; // more or less use_tcpip = TRUE; ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("es2ts",ii); err = int_value("es2ts",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-pid",argv[ii])) { CHECKARG("es2ts",ii); err = unsigned_value("es2ts",argv[ii],argv[ii+1],0,&video_pid); if (err) return 1; ii++; } else if (!strcmp("-pmt",argv[ii])) { CHECKARG("es2ts",ii); err = unsigned_value("es2ts",argv[ii],argv[ii+1],0,&pmt_pid); if (err) return 1; ii++; } else { fprint_err("### es2ts: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name && had_output_name) { fprint_err("### es2ts: Unexpected '%s'\n",argv[ii]); return 1; } else if (had_input_name) { output_name = argv[ii]; had_output_name = TRUE; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### es2ts: No input file specified\n"); return 1; } if (!had_output_name) { print_err("### es2ts: No output file specified\n"); return 1; } // Try to stop extraneous data ending up in our output stream if (use_stdout) { verbose = FALSE; quiet = TRUE; } if (use_stdin) err = open_elementary_stream(NULL,&es); else err = open_elementary_stream(input_name,&es); if (err) { print_err("### es2ts: " "Problem starting elementary stream - abandoning reading\n"); return 1; } if (!quiet) fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); // Decide if the input stream is H.262 or H.264 if (force_stream_type || use_stdin) { if (!quiet) print_msg("Reading input as "); } else { int video_type; err = decide_ES_file_video_type(es->input,FALSE,verbose,&video_type); if (err) { print_err("### es2ts: Error deciding on stream type\n"); close_elementary_stream(&es); return 1; } if (!quiet) print_msg("Input appears to be "); } switch (video_type) { case VIDEO_H262: stream_type = MPEG2_VIDEO_STREAM_TYPE; if (!quiet) print_msg("MPEG-2 (H.262)\n"); break; case VIDEO_H264: stream_type = AVC_VIDEO_STREAM_TYPE; if (!quiet) print_msg("MPEG-4/AVC (H.264)\n"); break; case VIDEO_AVS: stream_type = AVS_VIDEO_STREAM_TYPE; if (!quiet) print_msg("AVS\n"); break; case VIDEO_UNKNOWN: if (!quiet) print_msg("Unknown\n"); print_err("### es2ts: Input video type is not recognised\n"); close_elementary_stream(&es); return 1; } if (use_stdout) err = tswrite_open(TS_W_STDOUT,NULL,NULL,0,quiet,&output); else if (use_tcpip) err = tswrite_open(TS_W_TCP,output_name,NULL,port,quiet,&output); else err = tswrite_open(TS_W_FILE,output_name,NULL,0,quiet,&output); if (err) { close_elementary_stream(&es); fprint_err("### es2ts: Unable to open %s\n",output_name); return 1; } if (max && !quiet) fprint_msg("Stopping after %d ES data units\n",max); err = transfer_data(es,output,pmt_pid,video_pid,stream_type, max,verbose,quiet); if (err) print_err("### es2ts: Error transferring data\n"); close_elementary_stream(&es); // Closes the input file for us err2 = tswrite_close(output,quiet); if (err2) { fprint_err("### es2ts: Error closing output %s: %s\n",output_name, strerror(errno)); return 1; } if (err) return 1; else return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/es_defns.h000066400000000000000000000133161261471605300166330ustar00rootroot00000000000000/* * Datastructures for handling H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _es_defns #define _es_defns #include #include "compat.h" #include "pes_defns.h" // ------------------------------------------------------------ // A "file" offset in an ES stream, suitable for seeking to // For an ES based on a "bare" file, the `infile` value is all that is needed // For an ES based on a PES, the file offset of the PES packet and the byte // offset within that packet's ES data are needed. struct _ES_offset { offset_t infile; // as used by lseek int32_t inpacket; }; typedef struct _ES_offset ES_offset; typedef struct _ES_offset *ES_offset_p; #define SIZEOF_ES_OFFSET sizeof(struct _ES_offset) // The number of bytes to "read ahead" when reading directly from an // elementary stream #define ES_READ_AHEAD_SIZE 1000 // ------------------------------------------------------------ // A datastructure to represent our input elementary stream (ES) // (*output* elementary streams shouldn't need any particular housekeeping) struct elementary_stream { int reading_ES; // TRUE if we're reading ES data direct, FALSE if PES // If we're reading from an elementary data stream directly, then // we use the input directly int input; // And maintain a buffer of "read ahead" bytes byte read_ahead[ES_READ_AHEAD_SIZE]; offset_t read_ahead_posn; // location of this data in the file int32_t read_ahead_len; // actual number of bytes in the buffer // And the next byte to be read is specified by its offset in said // data stream. For "bare" ES data, the `infile` value is used to // remember the next bytes actual position in the file, and for PES // based ES data, the `inpacket` value is used to remember the next // bytes offset in the current PES packet. In both cases, the "unused" // quantity in the ES_offset is undefined. // (this is, in fact, more used by tsserve than by anything else) ES_offset posn_of_next_byte; // If we're reading from PES packets (from either a PS or TS file), // then we need to remember our PES reader PES_reader_p reader; byte *data; // Where we're reading our bytes from byte *data_end; // How to tell we've read them all byte *data_ptr; // And which byte we're interested in offset_t last_packet_posn; // Where the last PES packet was in the file int32_t last_packet_es_data_len; // And its number of ES bytes // Regardless, our triple byte memory is the same byte cur_byte; // The current (last read) byte byte prev1_byte; // The previous byte byte prev2_byte; // The byte before *that* }; typedef struct elementary_stream *ES_p; #define SIZEOF_ES sizeof(struct elementary_stream) // ------------------------------------------------------------ // And a representation of a single unit from the elementary stream // (whether an MPEG-2 (H.262) item, or an MPEG-4/AVC (H.264) NAL unit) // - basically, the thing that starts with a 00 00 01 prefix, and continues // to end of file or before the next 00 00 01 prefix (so note that it // contains any "trailing" 00 bytes). // // The normal way to acquire such a datastructure is via `build_ES_unit()`, // or `find_and_build_next_ES_unit()`. If instead you want to use the // address of a `struct ES_unit`, it is imperative that it be set up // correctly with `setup_ES_unit()` before it is passed to any of the // functions that use it, otherwise the contents will not be valid (and, // particularly, the "data" pointer will reference random memory). struct ES_unit { ES_offset start_posn; // The start of the current data unit byte *data; // Its data, including the leading 00 00 01 uint32_t data_len; // Its length uint32_t data_size; // The total buffer size byte start_code; // The byte after the 00 00 01 prefix // Something of a hack - if we were reading PES, did any of the PES packets // we read to make this ES unit contain a PTS? byte PES_had_PTS; }; typedef struct ES_unit *ES_unit_p; #define SIZEOF_ES_UNIT sizeof(struct ES_unit) // Start and increment sizes for the es_unit/data array. #define ES_UNIT_DATA_START_SIZE 1000 // was 500 #define ES_UNIT_DATA_INCREMENT 500 // was 100 // ------------------------------------------------------------ // An expandable list of ES units struct ES_unit_list { struct ES_unit *array; // The current array of ES units int length; // How many there are int size; // How big the array is }; typedef struct ES_unit_list *ES_unit_list_p; #define SIZEOF_ES_UNIT_LIST sizeof(struct ES_unit_list) #define ES_UNIT_LIST_START_SIZE 20 #define ES_UNIT_LIST_INCREMENT 20 #endif // _es_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/es_fns.h000066400000000000000000000350501261471605300163210ustar00rootroot00000000000000/* * Prototypes for handling H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _es_fns #define _es_fns #include "es_defns.h" // ============================================================ // Elementary stream functions - basic // ============================================================ /* * Open an ES file and build an elementary stream datastructure to read * it with. * * - `filename` is the ES files name * * Opens the file for read, builds the datastructure, and reads the first 3 * bytes of the input file (this is done to prime the triple-byte search * mechanism). * * Use `close_elementary_stream` to close the stream and the file. * * Returns 0 if all goes well, 1 otherwise. */ extern int open_elementary_stream(char *filename, ES_p *es); /* * Build an elementary stream datastructure attached to an input file. * This is intended for reading ES data files. * * - `input` is the file stream to read from. * * Builds the datastructure, and reads the first 3 bytes of the input * file (this is done to prime the triple-byte search mechanism). * * Use `free_elementary_stream` to release the ES context without closing * the associated file. * * Returns 0 if all goes well, 1 otherwise. */ extern int build_elementary_stream_file(int input, ES_p *es); /* * Build an elementary stream datastructure for use with a PES reader. * Reads the first (or next) three bytes of the ES. * * This reads data from the PES video data, ignoring any audio data. * * - `reader` is the PES reader we want to use to read our TS or PS data. * * The caller must explicitly close the PES reader as well as closing the * elementary stream (closing the ES does not affect the PES reader). * * Returns 0 if all goes well, 1 otherwise. */ extern int build_elementary_stream_PES(PES_reader_p reader, ES_p *es); /* * Tidy up the elementary stream datastructure after we've finished with it. * * Specifically: * * - free the datastructure * - set `es` to NULL * * No return status is given, since there's not much one can do if anything * *did* go wrong, and if something went wrong and the program is continuing, * it's bound to show up pretty soon. */ extern void free_elementary_stream(ES_p *es); /* * Tidy up the elementary stream datastructure after we've finished with it. * * Specifically: * * - close the input file (if its stream is set, and if it's not STDIN) * - call `free_elementary_stream()` * * No return status is given, since there's not much one can do if anything * *did* go wrong, and if something went wrong and the program is continuing, * it's bound to show up pretty soon. */ extern void close_elementary_stream(ES_p *es); /* * Ask an ES context if changed input is available. * * This is a convenience wrapper to save querying the ES context to see * if it is (a) reading from PES, (b) automatically writing the PES packets * out via a TS writer, and (c) if said TS writer has a changed command. * * Calls `tswrite_command_changed()` on the TS writer associated with this ES. * * Returns TRUE if there is a changed command. */ extern int es_command_changed(ES_p es); // ============================================================ // Elementary stream functions - item/unit reading // ============================================================ /* * Prepare the contents of a (new) ES unit datastructure. * * Allocates a new data array, and unsets the counts. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int setup_ES_unit(ES_unit_p unit); /* * Tidy up an ES unit datastructure after we've finished with it. * * (Frees the internal data array, and unsets the counts) */ extern void clear_ES_unit(ES_unit_p unit); /* * Build a new ES unit datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_ES_unit(ES_unit_p *unit); /* * Build a new ES unit datastructure, from a given data array. * * Takes a copy of 'data'. Sets 'start_code' appropriately, * sets 'start_posn' to (0,0), and 'PES_had_PTS' to FALSE. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_ES_unit_from_data(ES_unit_p *unit, byte *data, uint32_t data_len); /* * Tidy up and free an ES unit datastructure after we've finished with it. * * Empties the ES unit datastructure, frees it, and sets `unit` to NULL. * * If `unit` is already NULL, does nothing. */ extern void free_ES_unit(ES_unit_p *unit); /* * Print out some information this ES unit, on normal or error output */ extern void report_ES_unit(int is_msg, ES_unit_p unit); /* * Retrieve ES data from the end of a PES packet. It is assumed (i.e, things * will go wrong if it is not true) that at least one ES unit has been read * from the PES data stream via the ES reader. * * - `es` is our ES reader. It must be reading ES from PES packets. * - `data` is the ES data remaining (to be read) in the current PES packet. * It is up to the caller to free this data. * - `data_len` is the length of said data. If this is 0, then `data` * will be NULL. * * Returns 0 if all goes well, 1 if an error occurs. */ extern int get_end_of_underlying_PES_packet(ES_p es, byte **data, int *data_len); /* * Find and read in the next ES unit. * * In general, unless there are compelling reasons, use * `find_and_build_next_ES_unit()` instead. * * - `es` is the elementary stream we're reading from. * - `unit` is the datastructure into which to read the ES unit * - any previous content will be lost. * * Returns 0 if it succeeds, EOF if the end-of-file is read (i.e., there * is no next ES unit), otherwise 1 if some error occurs. */ extern int find_next_ES_unit(ES_p es, ES_unit_p unit); /* * Find and read the next ES unit into a new datastructure. * * - `es` is the elementary stream we're reading from. * - `count` is an integer to use as an id for this ES unit - typically * its index in the input stream * - `unit` is the datastructure containing the ES unit found, or NULL * if there was none. * * Returns 0 if it succeeds, EOF if the end-of-file is read (i.e., there * is no next ES unit), otherwise 1 if some error occurs. */ extern int find_and_build_next_ES_unit(ES_p es, ES_unit_p *unit); /* * Write (copy) the current ES unit to the output stream. * * Note that it writes out all of the data for this ES unit, * including its 00 00 01 start code prefix. * * - `output` is the output stream (file descriptor) to write to * - `unit` is the ES unit to write * * Returns 0 if all went well, 1 if something went wrong. */ extern int write_ES_unit(FILE *output, ES_unit_p unit); // ------------------------------------------------------------ // Arbitrary reading from ES data // ------------------------------------------------------------ /* * "Seek" to the given position in the ES data, which is assumed to * be an offset ready to read a 00 00 01 sequence. * * If the ES reader is using PES to read its data, then both fields * of `where` are significant, but if the underlying file *is* just a file, * only `where.infile` is used. * * Returns 0 if all went well, 1 is something went wrong */ extern int seek_ES(ES_p es, ES_offset where); /* * Read in some ES data from disk. * * Suitable for use when reading in a set of ES units whose bounds * (start offset and total number of bytes) have been remembered. * * "Seeks" to the given position in the ES data, which is assumed to * be an offset ready to read a 00 00 01 sequence, and reads data thereafter. * * After this function, the triple byte context is set to FF FF FF, and the * position of said bytes are undefined, but the next position to read a byte * from *is* defined. * * The intent is to allow the caller to have a data array (`data`) that * always contains the last data read, and is of the required size, and * need only be freed when no more data is needed. * * - `es` is where to read our data from * - `start_posn` is the file offset to start reading at * - `num_bytes` is how many bytes we want to read * - `data_len` may be NULL or a pointer to a value. * If it is NULL, then the data array will be reallocated to size * `num_bytes` regardless. If it is non-NULL, it should be passed *in* * as the size that `data` *was*, and will be returned as the size * that `data` is when the function returns. * - `data` is the data array to read into. If this is NULL, or if `num_bytes` * is NULL, or if `num_bytes` is greater than `data_len`, then it will be * reallocated to size `num_bytes`. * * Returns 0 if all went well, 1 if something went wrong. */ extern int read_ES_data(ES_p es, ES_offset start_posn, uint32_t num_bytes, uint32_t *data_len, byte **data); // ============================================================ // Lists of ES units // ============================================================ /* * Build a new list-of-ES-units datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_ES_unit_list(ES_unit_list_p *list); /* * Add a copy of an ES unit to the end of the ES unit list * * Note that since this takes a copy of the ES unit's data, it is safe * to free the original ES unit. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int append_to_ES_unit_list(ES_unit_list_p list, ES_unit_p unit); /* * Reset (empty) an ES unit list. */ extern void reset_ES_unit_list(ES_unit_list_p list); /* * Tidy up and free an ES unit list datastructure after we've finished with it. * * Clears the datastructure, frees it and returns `list` as NULL. * * Does nothing if `list` is already NULL. */ extern void free_ES_unit_list(ES_unit_list_p *list); /* * Report on an ES unit list's contents. * * - `name` is the name of the list (used in the header) * - `list` is the list to report on */ extern void report_ES_unit_list(char *name, ES_unit_list_p list); /* * Retrieve the bounds of this ES unit list in the file it was read from. * * - `list` is the ES unit list we're interested in * - `start` is its start position (i.e., the location at which to start * reading to retrieve all of the data for the list) * - `length` is the total length of the ES units within this list * * Returns 0 if all goes well, 1 if the ES unit list has no content. */ extern int get_ES_unit_list_bounds(ES_unit_list_p list, ES_offset *start, uint32_t *length); /* * Compare two ES unit lists. The comparison does not include the start * position of the unit data, but just the actual data - i.e., two unit lists * read from different locations in the input stream may be considered the * same if their data content is identical. * * - `list1` and `list2` are the two ES unit lists to compare. * * Returns TRUE if the lists contain identical content, FALSE otherwise. */ extern int same_ES_unit_list(ES_unit_list_p list1, ES_unit_list_p list2); /* * Compare two ES offsets * * Returns -1 if offset1 < offset2, 0 if they are the same, and 1 if * offset1 > offset2. */ extern int compare_ES_offsets(ES_offset offset1, ES_offset offset2); // ============================================================ // Simple file type guessing // ============================================================ /* * Look at the start of an elementary stream to try to determine its * video type. * * "Eats" the ES units that it looks at, and doesn't rewind the stream * afterwards. * * - `es` is the ES file * - if `print_dots` is true, print a dot for each ES unit that is inspected * - if `show_reasoning` is true, then output messages explaining how the * decision is being made * - `video_type` is the final decision -- one of VIDEO_H264, VIDEO_H262, * VIDEO_AVS, or VIDEO_UNKNOWN. * * Returns 0 if all goes well, 1 if something goes wrong */ extern int decide_ES_video_type(ES_p es, int print_dots, int show_reasoning, int *video_type); /* * Look at the start of an elementary stream to try to determine it's * video type. * * Note that it is easier to prove something is H.262 (or AVS) than to prove * that it is H.264, and that the result of this routine is a best-guess, not a * guarantee. * * Rewinds back to the original position in the file after it has finished. * * - `input` is the file to look at * - if `print_dots` is true, print a dot for each ES unit that is inspected * - if `show_reasoning` is true, then output messages explaining how the * decision is being made * - `video_type` is the final decision -- one of VIDEO_H264, VIDEO_H262, * VIDEO_AVS, or VIDEO_UNKNOWN. * * Returns 0 if all goes well, 1 if something goes wrong */ extern int decide_ES_file_video_type(int input, int print_dots, int show_reasoning, int *video_type); #endif // _es_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/esdots.c000066400000000000000000000704151261471605300163440ustar00rootroot00000000000000/* * Report on the contents of an H.264 (MPEG-4/AVC) or H.262 (MPEG-2) * elementary stream, as a sequence of single characters, representing * appropriate entities. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "es_fns.h" #include "pes_fns.h" #include "accessunit_fns.h" #include "h262_fns.h" #include "avs_fns.h" #include "printing_fns.h" #include "misc_fns.h" #include "version.h" double frame_rate = 25.0; // default frame rate. this can be modified using the switch "-fr" static inline int umod(unsigned int a, unsigned int b) { int r = a % b; return r < 0 ? r + b : r; } /* * Print out a single character representative of our item. */ static int h262_item_dot(h262_item_p item, double *delta_gop, int show_gop_time) { char *str = NULL; static int frames = 0; static int temp_frames = 0; int pic_coding_type = 0; // print the time every time we find a random access point (time between two GOPs) if (item->unit.start_code == 0xB3) { *delta_gop = (frames - temp_frames)/frame_rate; // time between two GOPs [in seconds] temp_frames = frames; if (show_gop_time && temp_frames) fprint_msg(": %2.4fs\n", *delta_gop); } if (item->unit.start_code == 0x00) { if (frames % ((int)frame_rate*60) == 0) fprint_msg("\n %d minute%s\n",frames/(int)(frame_rate*60), (frames/(int)(frame_rate*60)==1?"":"s")); frames++; } switch (item->unit.start_code) { case 0x00: str = (item->picture_coding_type==1?"i": item->picture_coding_type==2?"p": item->picture_coding_type==3?"b": item->picture_coding_type==4?"d":"x"); pic_coding_type = item->picture_coding_type; break; case 0xB0: str = "R"; break; // Reserved case 0xB1: str = "R"; break; // Reserved case 0xB2: str = "U"; break; // User data case 0xB3: str = "["; break; // SEQUENCE HEADER case 0xB4: str = "X"; break; // Sequence error case 0xB5: str = "E"; break; // Extension start case 0xB6: str = "R"; break; // Reserved case 0xB7: str = "]"; break; // SEQUENCE END case 0xB8: str = ">"; break; // Group start default: if (str == NULL) { if (item->unit.start_code >= 0x01 && item->unit.start_code <= 0xAF) return 0; //str = "."; // Don't report slice data explicitly else str = "?"; } break; } print_msg(str); fflush(stdout); return pic_coding_type; } /* * Simply report on the content of an MPEG2 file as single characters * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * * Returns 0 if it succeeds, 1 if some error occurs. */ static int report_h262_file_as_dots(ES_p es, int max, int verbose, int show_gop_time) { int err; int count = 0; double time_gop = 0.0; int gops = 0; double time_gop_max = 0.0; double time_gop_min = 1000.0; double time_gop_tot = 0.0; int pic_coding_type; unsigned long num_i = 0; // number of I frames unsigned long num_p = 0; // number of P frames unsigned long num_b = 0; // number of B frames if (verbose) print_msg("\n" "Each character represents a single H.262 item\n" "Pictures are represented according to their picture coding\n" "type, and the slices within a picture are not shown.\n" " i means an I picture\n" " p means a P picture\n" " b means a B picture\n" " d means a D picture (these should not occur in MPEG-2)\n" " x means some other picture (such should not occur)\n" "Other items are represented as follows:\n" " [ means a Sequence header\n" " > means a Group Start header\n" " E means an Extension start header\n" " U means a User data header\n" " X means a Sequence Error\n" " ] means a Sequence End\n" " R means a Reserved item\n" " ? means something else. This may indicate that the stream\n" " is not an ES representing H.262 (it might, for instance\n" " be PES)\n" "\n"); for (;;) { h262_item_p item; err = find_next_h262_item(es,&item); if (err == EOF) break; else if (err) { print_err("### Error copying NAL units\n"); return err; } count++; pic_coding_type = h262_item_dot(item, &time_gop, show_gop_time); switch (pic_coding_type) { case 1: num_i++; break; case 2: num_p++; break; case 3: num_b++; break; default: break; } if(item->unit.start_code == 0xB3) { time_gop_max = max(time_gop_max, time_gop); if (gops) time_gop_min = min(time_gop_min, time_gop); gops++; time_gop_tot += time_gop; } free_h262_item(&item); if (max > 0 && count >= max) break; } fprint_msg("\nFound %d MPEG2 item%s\n",count,(count==1?"":"s")); fprint_msg("%lu I, %lu P, %lu B\n",num_i,num_p,num_b); fprint_msg("GOP times (s): max=%2.4f, min=%2.4f, mean=%2.6f (frame rate = %2.2f)\n",time_gop_max, time_gop_min,time_gop_tot/(gops-1), frame_rate); return 0; } /* * Simply report on the content of an AVS file as single characters * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * * Returns 0 if it succeeds, 1 if some error occurs. */ static int report_avs_file_as_dots(ES_p es, int max, int verbose) { int err = 0; int count = 0; int frames = 0; //double frame_rate = 25.0; // as a guess avs_context_p context; if (verbose) print_msg("\n" "Each character represents a single AVS item\n" "Frames are represented according to their picture coding\n" "type, and the slices within a frame are not shown.\n" " i means an I frame\n" " p means a P frame\n" " b means a B frame\n" " _ means a (stray) slice, normally only at the start of a stream\n" " ! means something else (this should not be possible)\n" "Other items are represented as follows:\n" " [ means a Sequence header\n" " E means an Extension start header\n" " U means a User data header\n" " ] means a Sequence End\n" " V means a Video edit item\n" " ? means something else. This may indicate that the stream\n" " is not an ES representing AVS (it might, for instance\n" " be PES)\n" "\n"); err = build_avs_context(es,&context); if (err) return err; for (;;) { avs_frame_p avs_frame; err = get_next_avs_frame(context,TRUE,FALSE,&avs_frame); if (err == EOF) break; else if (err) { free_avs_context(&context); return 1; } if (avs_frame->is_frame) { frames ++; if (avs_frame->picture_coding_type == AVS_I_PICTURE_CODING) print_msg("i"); else if (avs_frame->picture_coding_type == AVS_P_PICTURE_CODING) print_msg("p"); else if (avs_frame->picture_coding_type == AVS_B_PICTURE_CODING) print_msg("b"); else print_msg("!"); // Give a *rough* guide as to timing -- assume a constant frame rate if (frames % (int)(frame_rate*60) == 0) fprint_msg("\n%d minute%s\n",frames/(25*60),(frames/(25*60)==1?"":"s")); } else if (avs_frame->start_code < 0xB0) print_msg("_"); // slice -- shouldn't happen else { switch (avs_frame->start_code) { case 0xB0: // sequence header frame_rate = avs_frame_rate(avs_frame->frame_rate_code); print_msg("["); break; case 0xB1: print_msg("]"); break; case 0xB2: print_msg("U"); break; case 0xB5: print_msg("E"); break; case 0xB7: print_msg("V"); break; default: /*print_msg("?");*/ fprint_msg("<%x>",avs_frame->start_code); break; } } fflush(stdout); count ++; free_avs_frame(&avs_frame); if (max > 0 && frames >= max) { fprint_msg("\nStopping because %d frames have been read\n",frames); break; } } fprint_msg("\nFound %d frame%s in %d AVS item%s\n", frames,(frames==1?"":"s"),count,(count==1?"":"s")); free_avs_context(&context); return 0; } /* * Returns a single character which specifies the type of the access unit * The value of gop_start_found says whether that unit is a recovery point */ static char choose_nal_type(access_unit_p access_unit, int *gop_start_found) { char character_nal_type = '?'; int ii; int gop_start = FALSE; nal_unit_p temp_nal_unit; int rec_point_required = FALSE; // FALSE: a random access point is identified as an I frame, // TRUE: a random access point is identified as an I frame + recovery_point SEI. // The value recovery_frame_cnt is never considered (as if it was 0). if (access_unit->primary_start == NULL) print_msg("_"); else if (access_unit->primary_start->nal_ref_idc == 0) { if (all_slices_I(access_unit)) character_nal_type = 'i'; else if (all_slices_P(access_unit)) character_nal_type = 'p'; else if (all_slices_B(access_unit)) character_nal_type = 'b'; else character_nal_type = 'x'; } else if (access_unit->primary_start->nal_unit_type == NAL_IDR) { gop_start = TRUE; if (all_slices_I(access_unit)) character_nal_type = 'D'; else character_nal_type = 'd'; } else if (access_unit->primary_start->nal_unit_type == NAL_NON_IDR) { if (all_slices_I(access_unit)) { character_nal_type = 'I'; if (!rec_point_required) gop_start = TRUE; else for (ii=0; iinal_units->length; ii++) { temp_nal_unit = access_unit->nal_units->array[ii]; if (temp_nal_unit->nal_unit_type == NAL_SEI) { if (temp_nal_unit->u.sei_recovery.payloadType == 6) { gop_start = TRUE; // Print a warning if more than one frame are needed for a // recovery point. This is technically legal but not supported // in our research of random access point. if (temp_nal_unit->u.sei_recovery.recovery_frame_cnt != 0) fprint_msg("!!! recovery_frame_cnt = %d\n", temp_nal_unit->u.sei_recovery.recovery_frame_cnt); } } } } else if (all_slices_P(access_unit)) character_nal_type = 'P'; else if (all_slices_B(access_unit)) character_nal_type = 'B'; else character_nal_type = 'X'; } *gop_start_found = gop_start; return character_nal_type; } /* * Report on data by access unit, as single characters * (access unit here means frame or coupled fields) * Returns 0 if all went well, 1 if something went wrong. */ static int dots_by_access_unit(ES_p es, int max, int verbose, int hash_eos, int show_gop_time) { int err = 0; int access_unit_count = 0; access_unit_context_p context; int gop_start_found = FALSE; int k_frame = 0; int size_gop; int size_gop_max = 0; int size_gop_min = 100000; int gops = 0; int size_gop_tot = 0; int is_first_k_frame = TRUE; char char_nal_type = 'a'; unsigned long num_idr = 0; unsigned long num_i = 0; unsigned long num_p = 0; unsigned long num_b = 0; if (verbose) print_msg("\n" "Each character represents a single access unit\n" "\n" " D means an IDR.\n" " d means an IDR that is not all I slices.\n" " I, P, B means all slices of the primary picture are I, P or B,\n" " and this is a reference picture.\n" " i, p, b means all slices of the primary picture are I, P or B,\n" " and this is NOT a reference picture.\n" " X or x means that not all slices are of the same type.\n" " ? means some other type of access unit.\n" " _ means that the access unit doesn't contain a primary picture.\n" "\n" "If -hasheos was specified:\n" " # means an EOS (end-of-stream) NAL unit.\n" "\n"); err = build_access_unit_context(es,&context); if (err) return err; for (;;) { access_unit_p access_unit; err = get_next_h264_frame(context,TRUE,FALSE,&access_unit); if (err == EOF) break; else if (err) { free_access_unit_context(&context); return 1; } char_nal_type = choose_nal_type(access_unit, &gop_start_found); // No real gop exists in h.264 but we try to find the distance between two // random access points. These can be: IDR frame or I frame with a // recovery_point in the SEI if (gop_start_found) { if (!is_first_k_frame) { size_gop = access_unit_count - k_frame; size_gop_max = max(size_gop_max, size_gop); size_gop_min = min(size_gop_min, size_gop); size_gop_tot += size_gop; gops++; if (show_gop_time) fprint_msg(": %2.4f\n", (double)size_gop/frame_rate ); // that's the time duration of a "GOP" // (if the frame rate is 25fps) } is_first_k_frame = FALSE; k_frame = access_unit_count; } switch (char_nal_type) { case 'I': case 'i': num_i++; break; case 'D': case 'd': num_idr++; break; case 'P': case 'p': num_p++; break; case 'B': case 'b': num_b++; break; default: break; } fprint_msg("%c", char_nal_type); access_unit_count++; fflush(stdout); free_access_unit(&access_unit); // Did the logical stream end after the last access unit? if (context->end_of_stream) { if (hash_eos) { print_msg("#"); // This should be enough to allow us to keep on after the EOS context->end_of_stream = FALSE; context->no_more_data = FALSE; } else { print_msg("\nStopping because found end-of-stream NAL unit\n"); break; } } if (max > 0 && context->nac->count >= max) { fprint_msg("\nStopping because %d NAL units have been read\n", context->nac->count); break; } } fprint_msg("\nFound %d NAL unit%s in %d access unit%s\n", context->nac->count,(context->nac->count==1?"":"s"), access_unit_count,(access_unit_count==1?"":"s")); fprint_msg("%lu IDR, %lu I, %lu P, %lu B access units\n",num_idr, num_i, num_p, num_b); if (gops) //only if there is more than 1 gop fprint_msg("GOP size (s): max=%2.4f, min=%2.4f, mean=%2.5f (frame rate = %2.2f)\n", (double)size_gop_max/frame_rate, (double)size_gop_min/frame_rate, (double)size_gop_tot/(frame_rate*gops), frame_rate); free_access_unit_context(&context); return 0; } /* * Simply report on the content of an ES file as single characters for each ES * unit * * - `es` is the input elementary stream * - `what_data` should be one of VIDEO_H262, VIDEO_H264 or VIDEO_AVS. * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * * Returns 0 if it succeeds, 1 if some error occurs. */ static int report_file_as_ES_dots(ES_p es, int what_data, int max, int verbose) { int err = 0; int count = 0; struct ES_unit unit; (void) setup_ES_unit(&unit); if (verbose) { print_msg("\n" "Each character represents a single ES unit\n"); switch (what_data) { case VIDEO_H262: print_msg("Pictures are represented according to their picture coding\n" "type, and the slices within a picture are not shown.\n" " i means an I picture\n" " p means a P picture\n" " b means a B picture\n" " d means a D picture (these should not occur in MPEG-2)\n" " ! means some other picture (such should not occur)\n" "Other items are represented as follows:\n" " [ means a Sequence header\n" " > means a Group Start header\n" " E means an Extension start header\n" " U means a User data header\n" " X means a Sequence Error\n" " ] means a Sequence End\n" " R means a Reserved item\n"); break; case VIDEO_H264: print_msg("### esdots: -es is not yet supported for H.264\n"); return 1; //break; case VIDEO_AVS: print_msg("Frames are represented according to their picture coding\n" "type, and the slices within a frame are not shown.\n" " i means an I frame\n" " p means a P frame\n" " b means a B frame\n" " _ means a slice\n" " ! means something else (this should not be possible)\n" "Other items are represented as follows:\n" " [ means a Sequence header\n" " E means an Extension start header\n" " U means a User data header\n" " ] means a Sequence End\n" " V means a Video edit item\n"); default: print_msg("### esdots: Unexpected type of data\n"); return 1; } print_msg(" ? means something else. This may indicate that the stream\n" " is not an ES representing AVS (it might, for instance\n" " be PES)\n" "\n"); } for (;;) { err = find_next_ES_unit(es,&unit); if (err == EOF) break; else if (err) return 1; switch (what_data) { case VIDEO_H262: switch (unit.start_code) { int picture_coding_type; case 0x00: picture_coding_type = (unit.data[5] & 0x38) >> 3; switch (picture_coding_type) { case 1: print_msg("i"); break; case 2: print_msg("p"); break; case 3: print_msg("b"); break; case 4: print_msg("d"); break; default: print_msg("!"); break; } break; case 0xB0: print_msg("R"); break; // Reserved case 0xB1: print_msg("R"); break; // Reserved case 0xB2: print_msg("U"); break; // User data case 0xB3: print_msg("["); break; // SEQUENCE HEADER case 0xB4: print_msg("X"); break; // Sequence error case 0xB5: print_msg("E"); break; // Extension start case 0xB6: print_msg("R"); break; // Reserved case 0xB7: print_msg("]"); break; // SEQUENCE END case 0xB8: print_msg(">"); break; // Group start default: if (unit.start_code >= 0x01 && unit.start_code <= 0xAF) print_msg("_"); else print_msg("?"); break; } break; case VIDEO_H264: break; case VIDEO_AVS: switch (unit.start_code) { case 0xB3: print_msg("i"); break; case 0xB6: switch (avs_picture_coding_type(&unit)) { case AVS_P_PICTURE_CODING: print_msg("p"); break; case AVS_B_PICTURE_CODING: print_msg("b"); break; default: print_msg("!"); break; } break; case 0xB0: print_msg("["); break; case 0xB1: print_msg("]"); break; case 0xB2: print_msg("U"); break; case 0xB5: print_msg("E"); break; case 0xB7: print_msg("V"); break; default: if (unit.start_code < 0xB0) print_msg("_"); else print_msg("?"); break; } default: /* shouldn't happen */ break; } fflush(stdout); count ++; if (max > 0 && count >= max) { fprint_msg("\nStopping because %d ES units have been read\n",count); break; } } clear_ES_unit(&unit); fprint_msg("\nFound %d ES units%s\n",count,(count==1?"":"s")); return 0; } static void print_usage() { print_msg( "Usage: esdots [switches] []\n" "\n" ); REPORT_VERSION("esdots"); print_msg( "\n" " Present the content of an H.264 (MPEG-4/AVC), H.262 (MPEG-2) or AVS\n" " elementary stream as a sequence of characters, representing access\n" " units/MPEG-2 items/AVS items.\n" "\n" " (Note that for H.264 it is access units and not frames that are\n" " represented, and for H.262 it is items and not pictures.)\n" "\n" "Files:\n" " is the Elementary Stream file (but see -stdin below)\n" "\n" "Switches:\n" " -verbose, -v Preface the output with an explanation of the\n" " characters being used.\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Take input from , instead of a named file\n" " -max , -m Maximum number of entities to read\n" " -pes, -ts The input file is TS or PS, to be read via the\n" " PES->ES reading mechanisms\n" " -hasheos Print a # on finding an EOS (end-of-stream) NAL unit\n" " rather than stopping (only applies to H.264)\n" " -es Report ES units, rather than any 'higher' unit\n" " (not necessarily suppported for all file types)\n" " -gop Show the duration of each GOP (for MPEG-2 steams)\n" " OR the distance between random access points (H.264)\n" " -fr Set the video frame rate (default = 25 fps)\n" "\n" "Stream type:\n" " If input is from a file, then the program will look at the start of\n" " the file to determine if the stream is H.264 or H.262 data. This\n" " process may occasionally come to the wrong conclusion, in which case\n" " the user can override the choice using the following switches.\n" "\n" " For AVS data, the program will never guess correctly, so the user must\n" " specify the file type, using -avs.\n" "\n" " If input is from standard input (via -stdin), then it is not possible\n" " for the program to make its own decision on the input stream type.\n" " Instead, it defaults to H.262, and relies on the user indicating if\n" " this is wrong.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" " -avs Force the program to treat the input as AVS.\n" ); } int main(int argc, char **argv) { char *input_name = NULL; int had_input_name = FALSE; int use_stdin = FALSE; int err = 0; ES_p es = NULL; int max = 0; int verbose = FALSE; int ii = 1; int use_pes = FALSE; int hash_eos = FALSE; int want_data = VIDEO_H262; int is_data = want_data; int force_stream_type = FALSE; int want_ES = FALSE; int show_gop_time = FALSE; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-help",argv[ii]) || !strcmp("-h",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-err",argv[ii])) { CHECKARG("esdots",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### esdots: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-stdin",argv[ii])) { had_input_name = TRUE; // more or less use_stdin = TRUE; } else if (!strcmp("-avc",argv[ii]) || !strcmp("-h264",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H264; } else if (!strcmp("-h262",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H262; } else if (!strcmp("-avs",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_AVS; } else if (!strcmp("-es",argv[ii])) want_ES = TRUE; else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) verbose = TRUE; else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("esdots",ii); err = int_value("esdots",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-hasheos",argv[ii])) hash_eos = TRUE; else if (!strcmp("-pes",argv[ii]) || !strcmp("-ts",argv[ii])) use_pes = TRUE; else if (!strcmp("-gop",argv[ii])) show_gop_time = TRUE; else if (!strcmp("-fr",argv[ii])) { CHECKARG("esdots",ii); err = double_value("esdots",argv[ii],argv[ii+1],TRUE,&frame_rate); if (err) return 1; ii++; } else { fprint_err("### esdots: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name) { fprint_err("### esdots: Unexpected '%s'\n",argv[ii]); return 1; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### esdots: No input file specified\n"); return 1; } err = open_input_as_ES((use_stdin?NULL:input_name),use_pes,FALSE, force_stream_type,want_data,&is_data,&es); if (err) { print_err("### esdots: Error opening input file\n"); return 1; } if (want_ES) err = report_file_as_ES_dots(es,is_data,max,verbose); else if (is_data == VIDEO_H262) err = report_h262_file_as_dots(es,max,verbose,show_gop_time); else if (is_data == VIDEO_H264) err = dots_by_access_unit(es,max,verbose,hash_eos,show_gop_time); else if (is_data == VIDEO_AVS) err = report_avs_file_as_dots(es,max,verbose); else { print_err("### esdots: Unexpected type of video data\n"); } if (err) { print_err("### esdots: Error producing 'dots'\n"); (void) close_input_as_ES(input_name,&es); return 1; } err = close_input_as_ES(input_name,&es); if (err) { print_err("### esdots: Error closing input file\n"); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/esfilter.c000066400000000000000000001013201261471605300166460ustar00rootroot00000000000000/* * An example application, reading an H.264 ES and doing things with it. * * Incorporates code to output an ES as an H.222 transport stream (TS). * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "es_fns.h" #include "pes_fns.h" #include "nalunit_fns.h" #include "ts_fns.h" #include "accessunit_fns.h" #include "h262_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "tswrite_fns.h" #include "filter_fns.h" #include "version.h" #define DEBUG 0 // Things this program can do - "actions" enum actions { ACTION_UNDEFINED, ACTION_COPY, ACTION_STRIP, ACTION_FILTER, }; typedef enum actions ACTION; /* * Copy the MPEG2 data to transport stream * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `as_TS`, then copy as transport stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if it succeeds, 1 if some error occurs. */ static int copy_h262(ES_p es, WRITER output, int as_TS, int max, int quiet) { int err; int count = 0; for (;;) { h262_item_p item; err = find_next_h262_item(es,&item); if (err == EOF) break; else if (err) { print_err("### Error copying NAL units\n"); return err; } count++; if (as_TS) err = write_ES_as_TS_PES_packet(output.ts_output,item->unit.data, item->unit.data_len,DEFAULT_VIDEO_PID, DEFAULT_VIDEO_STREAM_ID); else err = write_ES_unit(output.es_output,&(item->unit)); if (err) { print_err("### Error writing MPEG2 item\n"); return err; } free_h262_item(&item); if (max > 0 && count >= max) break; } if (!quiet) fprint_msg("Copied %d MPEG2 item%s\n",count,(count==1?"":"s")); return 0; } /* * Output an H.262 picture, appropriately. * * - `output` is the output stream * - if `as_TS`, write as transport stream * - `picture` is the H.262 picture to write out * * Returns 0 if it succeeds, 1 if some error occurs. */ static int write_h262_picture(WRITER output, int as_TS, h262_picture_p picture) { int err; if (as_TS) err = write_h262_picture_as_TS(output.ts_output,picture,DEFAULT_VIDEO_PID); else err = write_h262_picture_as_ES(output.es_output,picture); if (err) { print_err("### Error writing out H.262 picture\n"); return err; } return 0; } /* * Output just the I pictures. * * If `keep_P`, keep P pictures as well. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `as_TS`, write as transport stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `keep_p` is true, P pictures will be kept * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if it succeeds, 1 if some error occurs. */ static int strip_h262(ES_p es, WRITER output, int as_TS, int max, int keep_p, int verbose, int quiet) { int err; int count; h262_context_p h262 = NULL; h262_filter_context_p fcontext = NULL; // Keep a count of the pictures we encounter, regardless of picture type // (but note that, for the moment at least, we don't distinguish frame // and field pictures, which maybe we should) int pictures_seen = 0; // And how many pictures (i.e., I pictures) we keep int pictures_kept = 0; err = build_h262_context(es,&h262); if (err) { print_err("### Unable to build H.262 picture reading context\n"); return 1; } err = build_h262_filter_context_strip(&fcontext,h262,keep_p); if (err) { print_err("### Unable to build filter context\n"); free_h262_context(&h262); return 1; } for (count = 1; ; count++) { h262_picture_p seq_hdr = NULL; h262_picture_p picture = NULL; int delta_pictures_seen; err = get_next_stripped_h262_frame(fcontext,verbose,quiet, &seq_hdr,&picture,&delta_pictures_seen); if (err == EOF) { if (!quiet) print_msg("EOF\n"); break; } else if (err) { print_err("### Error getting next stripped picture\n"); free_h262_filter_context(&fcontext); free_h262_context(&h262); return 1; } pictures_seen += delta_pictures_seen; pictures_kept ++; if (seq_hdr != NULL) { err = write_h262_picture(output,as_TS,seq_hdr); if (err) { print_err("### Error writing picture\n"); free_h262_picture(&picture); free_h262_filter_context(&fcontext); free_h262_context(&h262); return 1; } } err = write_h262_picture(output,as_TS,picture); if (err) { print_err("### Error writing picture\n"); free_h262_picture(&picture); free_h262_filter_context(&fcontext); free_h262_context(&h262); return 1; } free_h262_picture(&picture); if (max > 0 && count >= max) { if (!quiet) fprint_msg("Ending after %d pictures\n",count); break; } } free_h262_filter_context(&fcontext); free_h262_context(&h262); if (!quiet) { fprint_msg("Found %d frames, kept %d (%.1f%%)\n", pictures_seen,pictures_kept, 100.0*pictures_kept/pictures_seen); } return 0; } /* * Filter the MPEG2 data, keeping just the I pictures, but aiming for * an "apparent" kept frequency as stated. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `as_TS`, write as transport stream * - `frequency` says how often we would like retained pictures to occur, * ideally - i.e., try to keep every th picture. The effect * should be similar to viewing the video stream at a speed up of * `frequency` times. * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if it succeeds, 1 if some error occurs. */ static int filter_h262(ES_p es, WRITER output, int as_TS, int frequency, int max, int verbose, int quiet) { int err; int count = 0; h262_context_p h262 = NULL; h262_filter_context_p fcontext = NULL; // Keep a count of the pictures we encounter, regardless of picture type // (but note that, for the moment at least, we don't distinguish frame // and field pictures, which maybe we should) int pictures_seen = 0; // And how many pictures (i.e., I pictures) we keep int pictures_kept = 0; // And how many we wrote int pictures_written = 0; h262_picture_p this_picture = NULL; h262_picture_p last_picture = NULL; h262_picture_p seq_hdr = NULL; // *We* mustn't free this one err = build_h262_context(es,&h262); if (err) { print_err("### Unable to build H.262 picture reading context\n"); return 1; } err = build_h262_filter_context(&fcontext,h262,frequency); if (err) { print_err("### Unable to build filter context\n"); free_h262_context(&h262); return 1; } for (count = 1; ; count++) { int delta_pictures_seen; err = get_next_filtered_h262_frame(fcontext,verbose,quiet,&seq_hdr, &this_picture,&delta_pictures_seen); if (err == EOF) { free_h262_picture(&last_picture); break; } else if (err) { print_err("### Error getting next filtered picture\n"); free_h262_picture(&last_picture); free_h262_filter_context(&fcontext); free_h262_context(&h262); return 1; } pictures_seen += delta_pictures_seen; if (this_picture == NULL) { // We need to repeat the last picture this_picture = last_picture; last_picture = NULL; } else pictures_kept ++; if (seq_hdr != NULL) { err = write_h262_picture(output,as_TS,seq_hdr); if (err) { print_err("### Error writing sequence header\n"); free_h262_picture(&this_picture); free_h262_picture(&last_picture); free_h262_filter_context(&fcontext); free_h262_context(&h262); return 1; } } if (this_picture != NULL) { err = write_h262_picture(output,as_TS,this_picture); if (err) { print_err("### Error writing picture\n"); free_h262_picture(&this_picture); free_h262_picture(&last_picture); free_h262_filter_context(&fcontext); free_h262_context(&h262); return 1; } pictures_written ++; } free_h262_picture(&last_picture); last_picture = this_picture; if (max > 0 && count >= max) { if (!quiet) fprint_msg("Ending after %d frames\n",count); free_h262_picture(&this_picture); break; } } free_h262_filter_context(&fcontext); free_h262_context(&h262); if (!quiet) { print_msg("\n"); print_msg("Summary\n"); print_msg("=======\n"); print_msg(" Found Kept Written\n"); fprint_msg("Frames %10d %10d (%4.1f%%) %10d (%4.1f%%)\n", pictures_seen,pictures_kept, 100*(((double)pictures_kept)/pictures_seen), pictures_written, 100*(((double)pictures_written)/pictures_seen)); if (frequency != 0) fprint_msg("Target (frames) . %10d (%4.1f%%) at requested" " frequency %d\n",pictures_seen/frequency, 100.0/frequency,frequency); } return 0; } /* * Copy the data as NAL units. * * Returns 0 if all went well, 1 if something went wrong. */ static int copy_nal_units(ES_p es, WRITER output, int as_TS, int max, int verbose, int quiet) { int err = 0; nal_unit_context_p context = NULL; err = build_nal_unit_context(es,&context); if (err) { print_err("### Unable to build NAL unit context to read ES\n"); return 1; } for (;;) { nal_unit_p nal; if (max > 0 && context->count >= max) break; err = find_next_NAL_unit(context,verbose,&nal); if (err == EOF) break; else if (err == 2) { print_err("!!! Ignoring broken NAL unit\n"); continue; } else if (err) { print_err("### Error getting next NAL unit\n"); free_nal_unit_context(&context); return err; } if (as_TS) err = write_NAL_unit_as_TS(output.ts_output,nal,DEFAULT_VIDEO_PID); else err = write_NAL_unit_as_ES(output.es_output,nal); if (err) { free_nal_unit(&nal); print_err("### Error copying NAL units\n"); free_nal_unit_context(&context); return err; } free_nal_unit(&nal); } if (!quiet) fprint_msg("Processed %d NAL unit%s\n", context->count,(context->count==1?"":"s")); free_nal_unit_context(&context); return 0; } /* * Output just IDR, I and maybe P access units. * * Access units are kept if they are reference frames, and if they are * IDR frames, or all of their slices are I (or maybe P). * * Returns 0 if all went well, 1 if something went wrong. */ static int strip_access_units(ES_p es, WRITER output, int as_TS, int max, int keep_all_ref, int verbose, int quiet) { int err = 0; int count; access_unit_context_p acontext = NULL; h264_filter_context_p fcontext = NULL; // It's nice to output some statistics at the end int access_units_seen = 0; int access_units_kept = 0; err = build_access_unit_context(es,&acontext); if (err) { print_err("### Unable to build access unit context\n"); return 1; } err = build_h264_filter_context_strip(&fcontext,acontext,keep_all_ref); if (err) { print_err("### Unable to build filter context\n"); free_access_unit_context(&acontext); return 1; } for (count = 1; ; count++) { access_unit_p access_unit = NULL; int delta_access_units_seen; err = get_next_stripped_h264_frame(fcontext,verbose,quiet, &access_unit, &delta_access_units_seen); if (err == EOF) break; else if (err) { print_err("### Error getting next stripped picture\n"); free_h264_filter_context(&fcontext); free_access_unit_context(&acontext); return 1; } access_units_seen += delta_access_units_seen; access_units_kept ++; if (as_TS) err = write_access_unit_as_TS(access_unit,fcontext->access_unit_context, output.ts_output,DEFAULT_VIDEO_PID); else err = write_access_unit_as_ES(access_unit,fcontext->access_unit_context, output.es_output); if (err) { print_err("### Error writing picture\n"); free_h264_filter_context(&fcontext); free_access_unit_context(&acontext); return 1; } free_access_unit(&access_unit); if (max > 0 && count >= max) { if (!quiet) fprint_msg("Ending after %d frames\n",count); break; } } free_h264_filter_context(&fcontext); free_access_unit_context(&acontext); if (!quiet) { print_msg("\n"); print_msg("Summary\n"); print_msg("=======\n"); print_msg(" Found Written\n"); fprint_msg("Access units %10d %10d (%4.1f%%)\n", access_units_seen, access_units_kept, 100*(((double)access_units_kept)/access_units_seen)); } return 0; } /* * Filter out access units, aiming to keep one every `frequency`. * * Returns 0 if all went well, 1 if something went wrong. */ static int filter_access_units(ES_p es, WRITER output, int as_TS, int max, int frequency, int verbose, int quiet) { int err = 0; int count; access_unit_context_p acontext = NULL; h264_filter_context_p fcontext = NULL; // It's nice to output some statistics at the end int access_units_seen = 0; int access_units_kept = 0; int access_units_written = 0; access_unit_p this_access_unit = NULL; access_unit_p last_access_unit = NULL; err = build_access_unit_context(es,&acontext); if (err) { print_err("### Unable to build access unit context\n"); return 1; } err = build_h264_filter_context(&fcontext,acontext,frequency); if (err) { print_err("### Unable to build filter context\n"); free_access_unit_context(&acontext); return 1; } for (count = 1; ; count++) { int delta_access_units_seen; err = get_next_filtered_h264_frame(fcontext,verbose,quiet, &this_access_unit, &delta_access_units_seen); if (err == EOF) break; else if (err) { print_err("### Error getting next filtered picture\n"); free_access_unit(&last_access_unit); free_h264_filter_context(&fcontext); free_access_unit_context(&acontext); return 1; } access_units_seen += delta_access_units_seen; if (this_access_unit == NULL) { // We need to repeat the last access unit this_access_unit = last_access_unit; last_access_unit = NULL; } else access_units_kept ++; if (this_access_unit != NULL) { if (as_TS) err = write_access_unit_as_TS(this_access_unit, fcontext->access_unit_context, output.ts_output,DEFAULT_VIDEO_PID); else err = write_access_unit_as_ES(this_access_unit, fcontext->access_unit_context, output.es_output); if (err) { print_err("### Error writing picture\n"); free_access_unit(&this_access_unit); free_access_unit(&last_access_unit); free_h264_filter_context(&fcontext); free_access_unit_context(&acontext); return 1; } access_units_written ++; } free_access_unit(&last_access_unit); last_access_unit = this_access_unit; if (max > 0 && count >= max) { if (!quiet) fprint_msg("Ending after %d frames\n",count); free_access_unit(&this_access_unit); break; } } free_h264_filter_context(&fcontext); free_access_unit_context(&acontext); if (!quiet) { print_msg("\n"); print_msg("Summary\n"); print_msg("=======\n"); print_msg(" Found Kept Written\n"); fprint_msg("Frames %10d %10d (%4.1f%%) %10d (%4.1f%%)\n", access_units_seen, access_units_kept, 100*(((double)access_units_kept)/access_units_seen), access_units_written, 100*(((double)access_units_written)/access_units_seen)); if (frequency != 0) fprint_msg("Target (frames) . %10d (%4.1f%%) at requested" " frequency %d\n",access_units_seen/frequency, 100.0/frequency,frequency); } return 0; } /* * Perform whatever action we have been requested to do on the input * stream. * * Returns 0 if all went well, 1 if something went wrong. */ static int do_action(ACTION action, ES_p es, WRITER output, int max, int frequency, int is_h262, int as_TS, int keep_all_ref, byte stream_type, int verbose, int quiet) { int err = 0; // If we're writing Transport Stream, start with the PAT and PMT if (as_TS) { if (!quiet) fprint_msg("Using transport stream id 1, PMT PID %#x, program 1 =" " PID %#x, stream type %#x\n",DEFAULT_PMT_PID,DEFAULT_VIDEO_PID, stream_type); err = write_TS_program_data(output.ts_output,1,1, DEFAULT_PMT_PID,DEFAULT_VIDEO_PID,stream_type); if (err) return 1; } switch (action) { case ACTION_FILTER: if (is_h262) err = filter_h262(es,output,as_TS,frequency,max,verbose,quiet); else err = filter_access_units(es,output,as_TS,max,frequency,verbose,quiet); break; case ACTION_STRIP: if (is_h262) err = strip_h262(es,output,as_TS,max,keep_all_ref,verbose,quiet); else err = strip_access_units(es,output,as_TS,max,keep_all_ref,verbose,quiet); break; case ACTION_COPY: if (is_h262) err = copy_h262(es,output,as_TS,max,quiet); else err = copy_nal_units(es,output,as_TS,max,verbose,quiet); break; default: fprint_err("### Unexpected action %d\n",action); err = 1; break; } return err; } static void print_usage() { print_msg( "Usage: esfilter [actions/switches] [] []\n" "\n" ); REPORT_VERSION("esfilter"); print_msg( "\n" " Output a filtered or truncated version of an elementary stream.\n" " The input is either H.264 (MPEG-4/AVC) or H.262 (MPEG-2).\n" " The output is either an elementary stream, or an H.222 transport\n" " stream\n" "\n" " If output is to an H.222 Transport Stream, then fixed values for\n" " the PMT PID (0x66) and video PID (0x68) are used.\n" "\n" "Files:\n" " is the input elementary stream (but see -stdin below).\n" " is the output stream, either an equivalent elementary\n" " stream, or an H.222 Transport Stream (but see -stdout\n" " and -host below).\n" "\n" "Actions:\n" " -copy Copy the input data to the output file\n" " (mostly useful as a way of truncating data with -max)\n" " -filter Filter data from input to output, aiming to keep every\n" " th frame (where is specified by -freq).\n" " -strip For H.264, output just the IDR and I pictures, for H.262,\n" " output just the I pictures, but see -allref below.\n" "\n" "Switches:\n" " -verbose, -v Output extra (debugging) messages\n" " -quiet, -q Only output error messages\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Take input from , instead of a named file\n" " -stdout Write output to , instead of a named file\n" " Forces -quiet and -err stderr.\n" " -host , -host :\n" " Writes output (over TCP/IP) to the named ,\n" " instead of to a named file. If is not\n" " specified, it defaults to 88. Implies -tsout.\n" " -max , -m Maximum number of frames to read (for -filter\n" " and -strip), or ES units/NAL units (for -copy).\n" " -freq Specify the frequency of frames to try to keep\n" " with -filter. Defaults to 8.\n" " -allref With -strip, keep all reference pictures (H.264)\n" " or all I and P pictures (H.262)\n" " -tsout Output data as Transport Stream PES packets\n" " (the default is as Elementary Stream)\n" " -pes, -ts The input file is TS or PS, to be read via the\n" " PES->ES reading mechanisms. Not allowed with -stdin.\n" "\n" "Stream type:\n" " If input is from a file, then the program will look at the start of\n" " the file to determine if the stream is H.264 or H.262 data. This\n" " process may occasionally come to the wrong conclusion, in which case\n" " the user can override the choice using the following switches.\n" "\n" " If input is from standard input (via -stdin), then it is not possible\n" " for the program to make its own decision on the input stream type.\n" " Instead, it defaults to H.262, and relies on the user indicating if\n" " this is wrong.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" ); } int main(int argc, char **argv) { char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; char *action_switch = "None"; int use_stdin = FALSE; int use_stdout = FALSE; int use_tcpip = FALSE; int port = 88; // Useful default port number int err = 0; ES_p es = NULL; WRITER output; int max = 0; ACTION action = ACTION_UNDEFINED; int as_TS = FALSE; int keep_all_ref = FALSE; int frequency = 8; // The default as stated in the usage int quiet = FALSE; int verbose = FALSE; int ii = 1; int use_pes = FALSE; int want_data = VIDEO_H262; int is_data; int force_stream_type = FALSE; byte stream_type; if (argc < 2) { print_usage(); return 0; } output.es_output = NULL; while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-help",argv[ii]) || !strcmp("-h",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-avc",argv[ii]) || !strcmp("-h264",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H264; } else if (!strcmp("-h262",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H262; } else if (!strcmp("-pes",argv[ii]) || !strcmp("-ts",argv[ii])) use_pes = TRUE; else if (!strcmp("-copy",argv[ii])) { action = ACTION_COPY; action_switch = argv[ii]; } else if (!strcmp("-filter",argv[ii])) { action = ACTION_FILTER; action_switch = argv[ii]; } else if (!strcmp("-strip",argv[ii])) { action = ACTION_STRIP; action_switch = argv[ii]; } else if (!strcmp("-tsout",argv[ii])) as_TS = TRUE; else if (!strcmp("-stdin",argv[ii])) { had_input_name = TRUE; // more or less use_stdin = TRUE; } else if (!strcmp("-stdout",argv[ii])) { had_output_name = TRUE; // more or less use_stdout = TRUE; redirect_output_stderr(); } else if (!strcmp("-err",argv[ii])) { CHECKARG("esfilter",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### esfilter: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-host",argv[ii])) { CHECKARG("esfilter",ii); err = host_value("esfilter",argv[ii],argv[ii+1],&output_name,&port); if (err) return 1; had_output_name = TRUE; // more or less use_tcpip = TRUE; as_TS = TRUE; ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-allref",argv[ii])) { keep_all_ref = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("esfilter",ii); err = int_value("esfilter",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-freq",argv[ii])) { CHECKARG("esfilter",ii); err = int_value("esfilter",argv[ii],argv[ii+1],TRUE,10,&frequency); if (err) return 1; ii++; } else { fprint_err("### esfilter: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name && had_output_name) { fprint_err("### esfilter: Unexpected '%s'\n",argv[ii]); return 1; } else if (had_input_name) { output_name = argv[ii]; had_output_name = TRUE; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### esfilter: No input file specified\n"); return 1; } if (!had_output_name) { print_err("### esfilter: No output file specified\n"); return 1; } if (action == ACTION_UNDEFINED) { print_err("### esfilter: No action specified (-copy, -strip," " -filter)\n"); return 1; } // Try to stop extraneous data ending up in our output stream if (use_stdout) { verbose = FALSE; quiet = TRUE; } err = open_input_as_ES((use_stdin?NULL:input_name),use_pes,quiet, force_stream_type,want_data,&is_data,&es); if (err) { print_err("### esfilter: Error opening input file\n"); return 1; } // If we're reading via PES, then we can ignore all but the video // - this may make things slightly faster, and will allow us to ignore // any errors in the non-video packets if (use_pes) set_PES_reader_video_only(es->reader,TRUE); if (is_data == VIDEO_H262) stream_type = MPEG2_VIDEO_STREAM_TYPE; else if (is_data == VIDEO_H264) stream_type = AVC_VIDEO_STREAM_TYPE; else { print_err("### esfilter: Unexpected type of video data\n"); return 1; } if (as_TS) { if (use_stdout) err = tswrite_open(TS_W_STDOUT,NULL,NULL,0,quiet,&(output.ts_output)); else if (use_tcpip) err = tswrite_open(TS_W_TCP,output_name,NULL,port,quiet,&(output.ts_output)); else err = tswrite_open(TS_W_FILE,output_name,NULL,0,quiet,&(output.ts_output)); if (err) { fprint_err("### esfilter: Unable to open %s\n",output_name); (void) close_input_as_ES(input_name,&es); return 1; } } else { output.es_output = fopen(output_name,"wb"); if (output.es_output == NULL) { fprint_err("### esfilter: Unable to open output file %s: %s\n", output_name,strerror(errno)); (void) close_input_as_ES(input_name,&es); return 1; } if (!quiet) fprint_msg("Writing to %s\n",output_name); } if (!quiet) { if (as_TS) print_msg("Writing as Transport Stream\n"); if (action == ACTION_FILTER) fprint_msg("Filtering freqency %d\n",frequency); if (action == ACTION_STRIP) { if (want_data == VIDEO_H262) { if (keep_all_ref) print_msg("Just keeping I and P pictures\n"); else print_msg("Just keep I pictures\n"); } else { if (keep_all_ref) print_msg("Just keeping reference pictures\n"); else print_msg("Just keep IDR and I pictures\n"); } } if (max) fprint_msg("Stopping as soon after %d NAL units as possible\n",max); } err = do_action(action,es,output,max,frequency,want_data==VIDEO_H262, as_TS,keep_all_ref,stream_type,verbose,quiet); if (err) { fprint_err("### esfilter: Error doing '%s'\n",action_switch); (void) close_input_as_ES(input_name,&es); if (as_TS) (void) tswrite_close(output.ts_output,TRUE); else if (had_output_name && !use_stdout) { err = fclose(output.es_output); if (err) fprint_err("### esfilter: (Error closing output file %s: %s)\n", output_name,strerror(errno)); } return 1; } // And tidy up when we're finished if (as_TS) { err = tswrite_close(output.ts_output,quiet); if (err) { fprint_err("### esfilter: Error closing output file %s",output_name); (void) close_input_as_ES(input_name,&es); return 1; } } else if (!use_stdout) { errno = 0; err = fclose(output.es_output); if (err) { fprint_err("### esfilter: Error closing output file %s: %s\n", output_name,strerror(errno)); (void) close_input_as_ES(input_name,&es); return 1; } } err = close_input_as_ES(input_name,&es); if (err) { print_err("### esfilter: Error closing input file\n"); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/esmerge.c000066400000000000000000000720061261471605300164700ustar00rootroot00000000000000/* * Merge a video ES and an audio ES to produce TS. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "es_fns.h" #include "accessunit_fns.h" #include "avs_fns.h" #include "audio_fns.h" #include "ts_fns.h" #include "tswrite_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" // Default audio rates, in Hertz #define CD_RATE 44100 #define DAT_RATE 48000 // Video frame rate (frames per second) #define DEFAULT_VIDEO_FRAME_RATE 25 // Number of audio samples per frame // For ADTS this will either be 1024 or 960. It's believed that it will // actually, in practice, be 1024, and in fact the difference may not be // significant enough to worry about for the moment. #define ADTS_SAMPLES_PER_FRAME 1024 // For MPEG-1 audio layer 2, this is 1152 #define L2_SAMPLES_PER_FRAME 1152 // For AC-3 this is 256 * 6 #define AC3_SAMPLES_PER_FRAME (256 * 6) // ------------------------------------------------------------ #define TEST_PTS_DTS 0 #if TEST_PTS_DTS #include "pes_fns.h" static int check(uint64_t value) { int err; byte data[5]; uint64_t result; encode_pts_dts(data,2,value); err = decode_pts_dts(data,2,&result); if (err) return 1; if (value == result) fprint_msg("Value " LLU_FORMAT " OK\n",value); else { fprint_msg("Input " LLU_FORMAT ", output " LLU_FORMAT "\n",value,result); return 1; } return 0; } static int test_pts() { if (check(0)) return 1; if (check(1)) return 1; if (check(2)) return 1; if (check(3)) return 1; if (check(4)) return 1; if (check(5)) return 1; if (check(6)) return 1; if (check(7)) return 1; if (check(8)) return 1; if (check(100)) return 1; if (check(10000)) return 1; if (check(1000000)) return 1; if (check(100000000)) return 1; if (check(10000000000LL)) return 1; if (check(1000000000000LL)) return 1; return 0; } #endif // TEST_PTS_DTS static int is_avs_I_frame(avs_frame_p frame) { return (frame->is_frame && frame->start_code == 0xB3); } static int is_I_or_IDR_frame(access_unit_p frame) { return (frame->primary_start != NULL && frame->primary_start->nal_ref_idc != 0 && (frame->primary_start->nal_unit_type == NAL_IDR || all_slices_I(frame))); } /* * Merge the given elementary streams to the given output. * * Returns 0 if all goes well, 1 if something goes wrong. */ static int merge_with_avs(avs_context_p video_context, int audio_file, TS_writer_p output, int audio_type, int audio_samples_per_frame, int audio_sample_rate, double video_frame_rate, int pat_pmt_freq, int quiet, int verbose, int debugging) { int ii; int err; uint32_t prog_pids[2]; byte prog_type[2]; int video_frame_count = 0; int audio_frame_count = 0; uint32_t video_pts_increment = (uint32_t)(90000.0 / video_frame_rate); uint32_t audio_pts_increment = (90000 * audio_samples_per_frame) / audio_sample_rate; uint64_t video_pts = 0; uint64_t audio_pts = 0; // The "actual" times are just for information, so we aren't too worried // about accuracy - thus floating point should be OK. double audio_time = 0.0; double video_time = 0.0; int got_video = TRUE; int got_audio = TRUE; if (verbose) fprint_msg("Video PTS increment %u\n" "Audio PTS increment %u\n",video_pts_increment,audio_pts_increment); // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; ii<8; ii++) { err = write_TS_null_packet(output); if (err) return 1; } // Then write some program data // @@@ later on we might want to repeat this every so often prog_pids[0] = DEFAULT_VIDEO_PID; prog_pids[1] = DEFAULT_AUDIO_PID; prog_type[0] = AVS_VIDEO_STREAM_TYPE; switch (audio_type) { case AUDIO_ADTS: case AUDIO_ADTS_MPEG2: case AUDIO_ADTS_MPEG4: prog_type[1] = ADTS_AUDIO_STREAM_TYPE; break; case AUDIO_L2: prog_type[1] = MPEG2_AUDIO_STREAM_TYPE; break; case AUDIO_AC3: prog_type[1] = ATSC_DOLBY_AUDIO_STREAM_TYPE; break; default: // what else can we do? prog_type[1] = ADTS_AUDIO_STREAM_TYPE; break; } err = write_TS_program_data2(output, 1, // transport stream id 1, // program number DEFAULT_PMT_PID, DEFAULT_VIDEO_PID, // PCR pid 2,prog_pids,prog_type); if (err) { print_err("### Error writing out TS program data\n"); return 1; } while (got_video || got_audio) { avs_frame_p avs_frame; audio_frame_p aframe; // Start with a video frame if (got_video) { err = get_next_avs_frame(video_context,quiet,debugging,&avs_frame); if (err == EOF) { if (verbose) print_msg("EOF: no more video data\n"); got_video = FALSE; } else if (err) return 1; if (!avs_frame->is_frame) { // It's not actually a *picture* // If we can, update the video frame rate to what we're told if (avs_frame->is_sequence_header) video_frame_rate = avs_frame_rate(avs_frame->frame_rate_code); // And output the data right away err = write_avs_frame_as_TS(output,avs_frame,DEFAULT_VIDEO_PID); if (err) { free_avs_frame(&avs_frame); print_err("### Error writing AVS frame (sequence header/end)\n"); return 1; } continue; // look for a "proper" frame } } if (got_video) { video_time = video_frame_count / video_frame_rate; video_pts += video_pts_increment; video_frame_count ++; if (verbose) fprint_msg("\n%s video frame %5d (@ %.2fs, " LLU_FORMAT ")\n", (is_avs_I_frame(avs_frame)?"**":"++"), video_frame_count,video_time,video_pts); if (pat_pmt_freq && !(video_frame_count % pat_pmt_freq)) { if (verbose) { fprint_msg("\nwriting PAT and PMT (frame = %d, freq = %d).. ", video_frame_count, pat_pmt_freq); } err = write_TS_program_data2(output, 1, // tsid 1, // Program number DEFAULT_PMT_PID, DEFAULT_VIDEO_PID, // PCR pid 2, prog_pids, prog_type); } // PCR counts frames as seen in the stream, so is easy // The presentation and decoding time for B frames (if we ever get any) // could reasonably be the same as the PCR. // The presentation and decoding time for I and IDR frames is unlikely to // be the same as the PCR (since frames come out later...), but it may // work to pretend the PTS is the PCR plus a delay time (for decoding)... // We could output the timing information every video frame, // but might as well only do it on index frames. // (Actually, we *could* work out the proper PTS for I frames, but it's // easier just to add a delay to allow for progress through the decoder) if (is_avs_I_frame(avs_frame)) err = write_avs_frame_as_TS_with_pts_dts(avs_frame, output,DEFAULT_VIDEO_PID, TRUE,video_pts + 30000, TRUE,video_pts); else err = write_avs_frame_as_TS_with_PCR(avs_frame, output,DEFAULT_VIDEO_PID, video_pts,0); if (err) { free_avs_frame(&avs_frame); print_err("### Error writing AVS frame\n"); return 1; } free_avs_frame(&avs_frame); } if (!got_audio) continue; // Then output enough audio frames to make up to a similar time while (audio_pts < video_pts || !got_video) { err = read_next_audio_frame(audio_file,audio_type,&aframe); if (err == EOF) { if (verbose) print_msg("EOF: no more audio data\n"); got_audio = FALSE; break; } else if (err) return 1; audio_time = audio_frame_count * audio_samples_per_frame / (double)audio_sample_rate; audio_pts += audio_pts_increment; audio_frame_count ++; if (verbose) fprint_msg("** audio frame %5d (@ %.2fs, " LLU_FORMAT ")\n", audio_frame_count,audio_time,audio_pts); err = write_ES_as_TS_PES_packet_with_pts_dts(output,aframe->data, aframe->data_len, DEFAULT_AUDIO_PID, DEFAULT_AUDIO_STREAM_ID, TRUE,audio_pts, TRUE,audio_pts); if (err) { free_audio_frame(&aframe); print_err("### Error writing audio frame\n"); return 1; } free_audio_frame(&aframe); } } if (!quiet) { uint32_t video_elapsed = (uint32_t)((double)(100*video_frame_count)/video_frame_rate); uint32_t audio_elapsed = 100*audio_frame_count* audio_samples_per_frame/audio_sample_rate; fprint_msg("Read %d video frame%s, %.2fs elapsed (%dm %.2fs)\n", video_frame_count,(video_frame_count==1?"":"s"), video_elapsed/100.0,video_elapsed/6000,(video_elapsed%6000)/100.0); fprint_msg("Read %d audio frame%s, %.2fs elapsed (%dm %.2fs)\n", audio_frame_count,(audio_frame_count==1?"":"s"), audio_elapsed/100.0,audio_elapsed/6000,(audio_elapsed%6000)/100.0); } return 0; } /* * Merge the given elementary streams to the given output. * * Returns 0 if all goes well, 1 if something goes wrong. */ static int merge_with_h264(access_unit_context_p video_context, int audio_file, TS_writer_p output, int audio_type, int audio_samples_per_frame, int audio_sample_rate, int video_frame_rate, int pat_pmt_freq, int quiet, int verbose, int debugging) { int ii; int err; uint32_t prog_pids[2]; byte prog_type[2]; int video_frame_count = 0; int audio_frame_count = 0; uint32_t video_pts_increment = 90000 / video_frame_rate; uint32_t audio_pts_increment = (90000 * audio_samples_per_frame) / audio_sample_rate; uint64_t video_pts = 0; uint64_t audio_pts = 0; // The "actual" times are just for information, so we aren't too worried // about accuracy - thus floating point should be OK. double audio_time = 0.0; double video_time = 0.0; int got_video = TRUE; int got_audio = TRUE; if (verbose) fprint_msg("Video PTS increment %u\n" "Audio PTS increment %u\n",video_pts_increment,audio_pts_increment); // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; ii<8; ii++) { err = write_TS_null_packet(output); if (err) return 1; } // Then write some program data // @@@ later on we might want to repeat this every so often prog_pids[0] = DEFAULT_VIDEO_PID; prog_pids[1] = DEFAULT_AUDIO_PID; prog_type[0] = AVC_VIDEO_STREAM_TYPE; switch (audio_type) { case AUDIO_ADTS: case AUDIO_ADTS_MPEG2: case AUDIO_ADTS_MPEG4: prog_type[1] = ADTS_AUDIO_STREAM_TYPE; break; case AUDIO_L2: prog_type[1] = MPEG2_AUDIO_STREAM_TYPE; break; case AUDIO_AC3: prog_type[1] = ATSC_DOLBY_AUDIO_STREAM_TYPE; break; default: // what else can we do? prog_type[1] = ADTS_AUDIO_STREAM_TYPE; break; } err = write_TS_program_data2(output, 1, // transport stream id 1, // program number DEFAULT_PMT_PID, DEFAULT_VIDEO_PID, // PCR pid 2,prog_pids,prog_type); if (err) { print_err("### Error writing out TS program data\n"); return 1; } while (got_video || got_audio) { access_unit_p access_unit; audio_frame_p aframe; // Start with a video frame if (got_video) { err = get_next_h264_frame(video_context,quiet,debugging,&access_unit); if (err == EOF) { if (verbose) print_msg("EOF: no more video data\n"); got_video = FALSE; } else if (err) return 1; } if (got_video) { video_time = video_frame_count / (double) video_frame_rate; video_pts += video_pts_increment; video_frame_count ++; if (verbose) fprint_msg("\n%s video frame %5d (@ %.2fs, " LLU_FORMAT ")\n", (is_I_or_IDR_frame(access_unit)?"**":"++"), video_frame_count,video_time,video_pts); if (pat_pmt_freq && !(video_frame_count % pat_pmt_freq)) { if (verbose) { fprint_msg("\nwriting PAT and PMT (frame = %d, freq = %d).. ", video_frame_count, pat_pmt_freq); } err = write_TS_program_data2(output, 1, // tsid 1, // Program number DEFAULT_PMT_PID, DEFAULT_VIDEO_PID, // PCR pid 2, prog_pids, prog_type); } // PCR counts frames as seen in the stream, so is easy // The presentation and decoding time for B frames (if we ever get any) // could reasonably be the same as the PCR. // The presentation and decoding time for I and IDR frames is unlikely to // be the same as the PCR (since frames come out later...), but it may // work to pretend the PTS is the PCR plus a delay time (for decoding)... // We could output the timing information every video frame, // but might as well only do it on index frames. if (is_I_or_IDR_frame(access_unit)) err = write_access_unit_as_TS_with_pts_dts(access_unit,video_context, output,DEFAULT_VIDEO_PID, TRUE,video_pts+45000, TRUE,video_pts); else err = write_access_unit_as_TS_with_PCR(access_unit,video_context, output,DEFAULT_VIDEO_PID, video_pts,0); if (err) { free_access_unit(&access_unit); print_err("### Error writing access unit (frame)\n"); return 1; } free_access_unit(&access_unit); // Did the logical video stream end after the last access unit? if (video_context->end_of_stream) { if (verbose) print_msg("Found End-of-stream NAL unit\n"); got_video = FALSE; } } if (!got_audio) continue; // Then output enough audio frames to make up to a similar time while (audio_pts < video_pts || !got_video) { err = read_next_audio_frame(audio_file,audio_type,&aframe); if (err == EOF) { if (verbose) print_msg("EOF: no more audio data\n"); got_audio = FALSE; break; } else if (err) return 1; audio_time = audio_frame_count * audio_samples_per_frame / (double)audio_sample_rate; audio_pts += audio_pts_increment; audio_frame_count ++; if (verbose) fprint_msg("** audio frame %5d (@ %.2fs, " LLU_FORMAT ")\n", audio_frame_count,audio_time,audio_pts); err = write_ES_as_TS_PES_packet_with_pts_dts(output,aframe->data, aframe->data_len, DEFAULT_AUDIO_PID, DEFAULT_AUDIO_STREAM_ID, TRUE,audio_pts, TRUE,audio_pts); if (err) { free_audio_frame(&aframe); print_err("### Error writing audio frame\n"); return 1; } free_audio_frame(&aframe); } } if (!quiet) { uint32_t video_elapsed = 100*video_frame_count/video_frame_rate; uint32_t audio_elapsed = 100*audio_frame_count* audio_samples_per_frame/audio_sample_rate; fprint_msg("Read %d video frame%s, %.2fs elapsed (%dm %.2fs)\n", video_frame_count,(video_frame_count==1?"":"s"), video_elapsed/100.0,video_elapsed/6000,(video_elapsed%6000)/100.0); fprint_msg("Read %d audio frame%s, %.2fs elapsed (%dm %.2fs)\n", audio_frame_count,(audio_frame_count==1?"":"s"), audio_elapsed/100.0,audio_elapsed/6000,(audio_elapsed%6000)/100.0); } return 0; } static void print_usage() { print_msg( "Usage:\n" " esmerge \n" "\n" ); REPORT_VERSION("esmerge"); print_msg( "\n" " Merge the contents of two Elementary Stream (ES) files, one containing\n" " video data, and the other audio, to produce an output file containing\n" " Transport Stream (TS).\n" "\n" "Files:\n" " is the ES file containing video.\n" " is the ES file containing audio.\n" " is the resultant TS file.\n" "\n" "Switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -quiet, -q Only output error messages.\n" " -verbose, -v Output information about each audio/video frame.\n" " -x Output diagnostic information.\n" "\n" " -h264 The video stream is H.264 (the default)\n" " -avs The video stream is AVS\n" "\n" " -vidrate Video frame rate in Hz - defaults to 25Hz.\n" "\n" " -rate Audio sample rate in Hertz - defaults to 44100, i.e., 44.1KHz.\n" " -cd Equivalent to -rate 44100 (CD rate), the default.\n" " -dat Equivalent to -rate 48000 (DAT rate).\n" "\n" " -adts The audio stream is ADTS (the default)\n" " -l2 The audio stream is MPEG layer 2 audio\n" " -mp2adts The audio stream is MPEG-2 style ADTS regardless of ID bit\n" " -mp4adts The audio stream is MPEG-4 style ADTS regardless of ID bit\n" " -ac3 The audio stream is Dolby AC-3 in ATSC\n" "\n" " -patpmtfreq PAT and PMT will be inserted every video frames. \n" " by default, f = 0 and PAT/PMT are inserted only at \n" " the start of the output stream.\n" "\n" "Limitations\n" "===========\n" "For the moment, the video input must be H.264 or AVS, and the audio input\n" "ADTS, AC-3 ATSC or MPEG layer 2. Also, the audio is assumed to have a\n" "constant number of samples per frame.\n" ); } int main(int argc, char **argv) { int had_video_name = FALSE; int had_audio_name = FALSE; int had_output_name = FALSE; char *video_name = NULL; char *audio_name = NULL; char *output_name = NULL; int err = 0; ES_p video_es = NULL; access_unit_context_p h264_video_context = NULL; avs_context_p avs_video_context = NULL; int audio_file = -1; TS_writer_p output = NULL; int quiet = FALSE; int verbose = FALSE; int debugging = FALSE; int audio_samples_per_frame = ADTS_SAMPLES_PER_FRAME; int audio_sample_rate = CD_RATE; int video_frame_rate = DEFAULT_VIDEO_FRAME_RATE; int audio_type = AUDIO_ADTS; int video_type = VIDEO_H264; int pat_pmt_freq = 0; int ii = 1; #if TEST_PTS_DTS test_pts(); return 0; #endif if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-help",argv[ii]) || !strcmp("-h",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-err",argv[ii])) { CHECKARG("esmerge",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### esmerge: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { quiet = TRUE; } else if (!strcmp("-x",argv[ii])) { debugging = TRUE; quiet = FALSE; } else if (!strcmp("-rate",argv[ii])) { CHECKARG("esmerge",ii); err = int_value("esmerge",argv[ii],argv[ii+1],TRUE,10,&audio_sample_rate); if (err) return 1; ii++; } else if (!strcmp("-cd",argv[ii])) { audio_sample_rate = CD_RATE; } else if (!strcmp("-dat",argv[ii])) { audio_sample_rate = DAT_RATE; } else if (!strcmp("-vidrate",argv[ii])) { CHECKARG("esmerge",ii); err = int_value("esmerge",argv[ii],argv[ii+1],TRUE,10,&video_frame_rate); if (err) return 1; ii++; } else if (!strcmp("-adts",argv[ii])) { audio_type = AUDIO_ADTS; } else if (!strcmp("-l2",argv[ii])) { audio_type = AUDIO_L2; } else if (!strcmp("-ac3", argv[ii])) { audio_type = AUDIO_AC3; } else if (!strcmp("-h264",argv[ii])) { video_type = VIDEO_H264; } else if (!strcmp("-mp2adts", argv[ii])) { audio_type = AUDIO_ADTS_MPEG2; } else if (!strcmp("-mp4adts", argv[ii])) { audio_type = AUDIO_ADTS_MPEG4; } else if (!strcmp("-avs",argv[ii])) { video_type = VIDEO_AVS; } else if (!strcmp("-patpmtfreq", argv[ii])) { CHECKARG("esmerge",ii); err = int_value("esmerge", argv[ii], argv[ii+1], TRUE, 10, &pat_pmt_freq); if (err) { return 1; } ++ii; } else { fprint_err("### esmerge: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (!had_video_name) { video_name = argv[ii]; had_video_name = TRUE; } else if (!had_audio_name) { audio_name = argv[ii]; had_audio_name = TRUE; } else if (!had_output_name) { output_name = argv[ii]; had_output_name = TRUE; } else { fprint_err("### esmerge: Unexpected '%s'\n",argv[ii]); return 1; } } ii++; } if (!had_video_name) { print_err("### esmerge: No video input file specified\n"); return 1; } if (!had_audio_name) { print_err("### esmerge: No audio input file specified\n"); return 1; } if (!had_output_name) { print_err("### esmerge: No output file specified\n"); return 1; } err = open_elementary_stream(video_name,&video_es); if (err) { print_err("### esmerge: " "Problem starting to read video as ES - abandoning reading\n"); return 1; } if (video_type == VIDEO_H264) { err = build_access_unit_context(video_es,&h264_video_context); if (err) { print_err("### esmerge: " "Problem starting to read video as H.264 - abandoning reading\n"); close_elementary_stream(&video_es); return 1; } } else if (video_type == VIDEO_AVS) { err = build_avs_context(video_es,&avs_video_context); if (err) { print_err("### esmerge: " "Problem starting to read video as H.264 - abandoning reading\n"); close_elementary_stream(&video_es); return 1; } } else { print_err("### esmerge: Unknown video type\n"); return 1; } audio_file = open_binary_file(audio_name,FALSE); if (audio_file == -1) { print_err("### esmerge: " "Problem opening audio file - abandoning reading\n"); close_elementary_stream(&video_es); free_access_unit_context(&h264_video_context); free_avs_context(&avs_video_context); return 1; } err = tswrite_open(TS_W_FILE,output_name,NULL,0,quiet,&output); if (err) { fprint_err("### esmerge: " "Problem opening output file %s - abandoning reading\n", output_name); close_elementary_stream(&video_es); close_file(audio_file); free_access_unit_context(&h264_video_context); free_avs_context(&avs_video_context); return 1; } switch (audio_type) { case AUDIO_ADTS: audio_samples_per_frame = ADTS_SAMPLES_PER_FRAME; break; case AUDIO_L2: audio_samples_per_frame = L2_SAMPLES_PER_FRAME; break; case AUDIO_AC3: audio_samples_per_frame = AC3_SAMPLES_PER_FRAME; break; default: // hmm - or we could give up... audio_samples_per_frame = ADTS_SAMPLES_PER_FRAME; break; } if (!quiet) { fprint_msg("Reading video from %s\n",video_name); fprint_msg("Reading audio from %s (as %s)\n",audio_name,AUDIO_STR(audio_type)); fprint_msg("Writing output to %s\n",output_name); fprint_msg("Audio sample rate: %dHz (%.2fKHz)\n",audio_sample_rate, audio_sample_rate/1000.0); fprint_msg("Audio samples per frame: %d\n",audio_samples_per_frame); fprint_msg("Video frame rate: %dHz\n",video_frame_rate); } if (video_type == VIDEO_H264) err = merge_with_h264(h264_video_context,audio_file,output, audio_type, audio_samples_per_frame,audio_sample_rate, video_frame_rate, pat_pmt_freq, quiet,verbose,debugging); else if (video_type == VIDEO_AVS) err = merge_with_avs(avs_video_context,audio_file,output, audio_type, audio_samples_per_frame,audio_sample_rate, video_frame_rate, pat_pmt_freq, quiet,verbose,debugging); else { print_err("### esmerge: Unknown video type\n"); return 1; } if (err) { print_err("### esmerge: Error merging video and audio streams\n"); close_elementary_stream(&video_es); close_file(audio_file); free_access_unit_context(&h264_video_context); free_avs_context(&avs_video_context); (void) tswrite_close(output,quiet); return 1; } close_elementary_stream(&video_es); close_file(audio_file); free_access_unit_context(&h264_video_context); free_avs_context(&avs_video_context); err = tswrite_close(output,quiet); if (err) { fprint_err("### esmerge: Error closing output %s\n",output_name); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/esreport.c000066400000000000000000001101251261471605300166770ustar00rootroot00000000000000/* * Report on the contents of an H.264 (MPEG-4/AVC) or H.262 (MPEG-2) * elementary stream. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "es_fns.h" #include "nalunit_fns.h" #include "ts_fns.h" #include "pes_fns.h" #include "accessunit_fns.h" #include "h262_fns.h" #include "avs_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" #define FRAMES_PER_SECOND 25 #define FRAMES_PER_MINUTE (FRAMES_PER_SECOND * 60) /* * Report on the content of an AVS file * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` AVS items * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - if `count_sizes` is true, then a summary of frame sizes will be kept */ static void report_avs_frames(ES_p es, int max, int verbose, int quiet, int count_sizes) { int err; int count = 0; int num_frames = 0; int num_sequence_headers = 0; int num_sequence_ends = 0; uint32_t min_frame_size = 1000000; uint32_t max_frame_size = 0; uint32_t sum_frame_size = 0; // I, P, B = 0, 1, 2 (we "make up" picture coding type 0 for I frames) uint32_t min_x_frame_size[3] = {1000000,1000000,1000000}; uint32_t max_x_frame_size[3] = {0,0,0}; uint32_t sum_x_frame_size[3] = {0,0,0}; int num_x_frames[3] = {0,0,0}; uint32_t min_seq_hdr_size = 1000000; uint32_t max_seq_hdr_size = 0; uint32_t sum_seq_hdr_size = 0; ES_offset start; uint32_t length; avs_context_p avs; err = build_avs_context(es,&avs); if (err) { print_err("### Error trying to build AVS reader from ES reader\n"); return; } for (;;) { avs_frame_p frame; err = get_next_avs_frame(avs,verbose,quiet,&frame); if (err == EOF) break; else if (err) { print_err("### Error getting next AVS frame\n"); break; } count++; if (!quiet) report_avs_frame(frame,FALSE); else if (verbose) report_avs_frame(frame,TRUE); if (frame->is_frame) { if (count_sizes) { err = get_ES_unit_list_bounds(frame->list,&start,&length); if (err) break; if (min_frame_size > length) min_frame_size = length; if (max_frame_size < length) max_frame_size = length; sum_frame_size += length; if (frame->picture_coding_type < 3) // paranoia - check in array bounds { // I, P or B -- even though there isn't a "real" picture coding type // for I, we forge one when we read the frame int ii = frame->picture_coding_type; num_x_frames[ii] ++; if (min_x_frame_size[ii] > length) min_x_frame_size[ii] = length; if (max_x_frame_size[ii] < length) max_x_frame_size[ii] = length; sum_x_frame_size[ii] += length; } } num_frames ++; if (frame->picture_coding_type < 3) // paranoia - check in array bounds num_x_frames[frame->picture_coding_type] ++; } else if (frame->is_sequence_header) { if (count_sizes) { err = get_ES_unit_list_bounds(frame->list,&start,&length); if (err) break; if (min_seq_hdr_size > length) min_seq_hdr_size = length; if (max_seq_hdr_size < length) max_seq_hdr_size = length; sum_seq_hdr_size += length; } num_sequence_headers ++; } else num_sequence_ends ++; free_avs_frame(&frame); if (max > 0 && count >= max) break; } free_avs_context(&avs); fprint_msg("Found %d AVS 'frame'%s:\n" " %5d frame%s (%d I, %d P, %d B)\n" " %5d sequence header%s\n" " %5d sequence end%s\n", count,(count==1?"":"s"), num_frames,(num_frames==1?"":"s"), num_x_frames[AVS_I_PICTURE_CODING], num_x_frames[AVS_P_PICTURE_CODING], num_x_frames[AVS_B_PICTURE_CODING], num_sequence_headers,(num_sequence_headers==1?"":"s"), num_sequence_ends,(num_sequence_ends==1?"":"s")); { double total_seconds = num_frames / (double)FRAMES_PER_SECOND; int minutes = (int)(total_seconds / 60); double seconds = total_seconds - 60*minutes; fprint_msg("At 25 frames/second, that is %dm %.1fs (%.2fs)\n",minutes,seconds, total_seconds); } if (count_sizes) { int ii; if (num_frames > 0) fprint_msg("Frame sizes ranged from %5u to %7u bytes, mean %9.2f\n", min_frame_size,max_frame_size, sum_frame_size/(double)num_frames); for (ii = 0; ii < 3; ii++) { if (num_x_frames[ii] > 0) fprint_msg(" %s frames from %5u to %7u bytes, mean %9.2f\n", (ii==0?"I": ii==1?"P": ii==2?"B":"?"), min_x_frame_size[ii],max_x_frame_size[ii], sum_x_frame_size[ii]/(double)num_x_frames[ii]); } if (num_sequence_headers > 0) { if (min_seq_hdr_size == max_seq_hdr_size) fprint_msg("Sequence headers were all %u bytes\n",min_seq_hdr_size); else fprint_msg("Sequence headers from %5u to %7u bytes, mean %9.2f\n", min_seq_hdr_size,max_seq_hdr_size, sum_seq_hdr_size/(double)num_sequence_headers); } } } /* * Report on the ES units in a file * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported */ static void report_ES_units(ES_p es, int max, int verbose, int quiet) { int err; int count = 0; struct ES_unit unit; (void) setup_ES_unit(&unit); for (;;) { err = find_next_ES_unit(es,&unit); if (err == EOF) break; else if (err) { print_err("### Error finding next ES unit\n"); break; } count++; if (!quiet) report_ES_unit(TRUE,&unit); if (verbose) print_data(TRUE," Data", unit.data,unit.data_len,10); if (max > 0 && count >= max) break; } clear_ES_unit(&unit); fprint_msg("Found %d ES unit%s\n",count,(count==1?"":"s")); } /* * Report on the content of an MPEG2 file * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported */ static void find_h262_fields(ES_p es, int max, int verbose) { int err; int count = 0; int num_fields = 0; int num_frames = 0; int num_sequence_headers = 0; int num_sequence_ends = 0; h262_context_p h262; err = build_h262_context(es,&h262); if (err) { print_err("### Error trying to build H.262 reader from ES reader\n"); return; } for (;;) { h262_picture_p picture; err = get_next_h262_single_picture(h262,verbose,&picture); if (err == EOF) break; else if (err) { print_err("### Error getting next H.262 picture\n"); break; } count++; if (picture->is_picture) { if (picture->picture_structure < 3) { report_h262_picture(picture,verbose); num_fields ++; } else num_frames ++; } else if (picture->is_sequence_header) num_sequence_headers ++; else num_sequence_ends ++; free_h262_picture(&picture); if (max > 0 && count >= max) break; } free_h262_context(&h262); fprint_msg("Found %d MPEG-2 'picture'%s:\n" " %5d field%s\n" " %5d frame%s\n" " %5d sequence header%s\n" " %5d sequence end%s\n", count,(count==1?"":"s"), num_fields,(num_fields==1?"":"s"), num_frames,(num_frames==1?"":"s"), num_sequence_headers,(num_sequence_headers==1?"":"s"), num_sequence_ends,(num_sequence_ends==1?"":"s")); } /* * Report on the content of an MPEG2 file * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - if `count_sizes` is true, then a summary of frame sizes will be kept */ static void report_h262_frames(ES_p es, int max, int verbose, int quiet, int count_sizes) { int err; int count = 0; int num_frames = 0; int num_sequence_headers = 0; int num_sequence_ends = 0; uint32_t min_frame_size = 1000000; uint32_t max_frame_size = 0; uint32_t sum_frame_size = 0; // I=1, P=2, B=3, D=4 -- so subtract one before using the picture coding type // as an index into the arrays... uint32_t min_x_frame_size[4] = {1000000,1000000,1000000,1000000}; uint32_t max_x_frame_size[4] = {0,0,0,0}; uint32_t sum_x_frame_size[4] = {0,0,0,0}; int num_x_frames[4] = {0,0,0,0}; uint32_t min_seq_hdr_size = 1000000; uint32_t max_seq_hdr_size = 0; uint32_t sum_seq_hdr_size = 0; ES_offset start; uint32_t length; h262_context_p h262; err = build_h262_context(es,&h262); if (err) { print_err("### Error trying to build H.262 reader from ES reader\n"); return; } for (;;) { h262_picture_p picture; err = get_next_h262_frame(h262,verbose,quiet,&picture); if (err == EOF) break; else if (err) { print_err("### Error getting next H.262 picture\n"); break; } count++; if (!quiet) report_h262_picture(picture,FALSE); else if (verbose) report_h262_picture(picture,TRUE); if (picture->is_picture) { if (count_sizes) { err = get_ES_unit_list_bounds(picture->list,&start,&length); if (err) break; if (min_frame_size > length) min_frame_size = length; if (max_frame_size < length) max_frame_size = length; sum_frame_size += length; if (picture->picture_coding_type < 5 && picture->picture_coding_type > 0) // paranoia - check for array bounds { // I, P, B or D frame int ii = picture->picture_coding_type - 1; if (min_x_frame_size[ii] > length) min_x_frame_size[ii] = length; if (max_x_frame_size[ii] < length) max_x_frame_size[ii] = length; sum_x_frame_size[ii] += length; } } num_frames ++; if (picture->picture_coding_type < 5 && picture->picture_coding_type > 0) // paranoia - check for array bounds num_x_frames[picture->picture_coding_type - 1] ++; } else if (picture->is_sequence_header) { if (count_sizes) { err = get_ES_unit_list_bounds(picture->list,&start,&length); if (err) break; if (min_seq_hdr_size > length) min_seq_hdr_size = length; if (max_seq_hdr_size < length) max_seq_hdr_size = length; sum_seq_hdr_size += length; } num_sequence_headers ++; } else num_sequence_ends ++; free_h262_picture(&picture); if (max > 0 && count >= max) break; } free_h262_context(&h262); fprint_msg("Found %d MPEG-2 'picture'%s:\n" " %5d frame%s (%d I, %d P, %d B, %d D)\n" " %5d sequence header%s\n" " %5d sequence end%s\n", count,(count==1?"":"s"), num_frames,(num_frames==1?"":"s"), num_x_frames[0], num_x_frames[1], num_x_frames[2], num_x_frames[3], num_sequence_headers,(num_sequence_headers==1?"":"s"), num_sequence_ends,(num_sequence_ends==1?"":"s")); { double total_seconds = num_frames / (double)FRAMES_PER_SECOND; int minutes = (int)(total_seconds / 60); double seconds = total_seconds - 60*minutes; fprint_msg("At 25 frames/second, that is %dm %.1fs (%.2fs)\n",minutes,seconds, total_seconds); } if (count_sizes) { int ii; if (num_frames > 0) fprint_msg("Frame sizes ranged from %5u to %7u bytes, mean %9.2f\n", min_frame_size,max_frame_size, sum_frame_size/(double)num_frames); for (ii = 0; ii < 4; ii++) { if (num_x_frames[ii] > 0) fprint_msg(" %s frames from %5u to %7u bytes, mean %9.2f\n", H262_PICTURE_CODING_STR(ii), min_x_frame_size[ii],max_x_frame_size[ii], sum_x_frame_size[ii]/(double)num_x_frames[ii]); } if (num_sequence_headers > 0) { if (min_seq_hdr_size == max_seq_hdr_size) fprint_msg("Sequence headers were all %u bytes\n",min_seq_hdr_size); else fprint_msg("Sequence headers from %5u to %7u bytes, mean %9.2f\n", min_seq_hdr_size,max_seq_hdr_size, sum_seq_hdr_size/(double)num_sequence_headers); } } } /* * Report on changes in AFD in an MPEG2 file * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported */ static void report_h262_afds(ES_p es, int max, int verbose, int quiet) { int err; int frames = 0; byte afd = 0; // not '1000', so we see the first value h262_context_p h262; int report_every = 5 * FRAMES_PER_MINUTE; err = build_h262_context(es,&h262); if (err) { print_err("### Error trying to build H.262 reader from ES reader\n"); return; } for (;;) { h262_picture_p picture; err = get_next_h262_frame(h262,verbose,quiet,&picture); if (err == EOF) break; else if (err) { print_err("### Error getting next H.262 picture\n"); break; } if (picture->is_picture) { // NB: the time at which the frame *starts* if (frames % report_every == 0) fprint_msg("%d minute%s\n",frames/FRAMES_PER_MINUTE, (frames/FRAMES_PER_MINUTE==1?"":"s")); frames ++; } if (picture->is_picture && picture->afd != afd) { double total_seconds = frames / (double)FRAMES_PER_SECOND; int minutes = (int)(total_seconds / 60); double seconds = total_seconds - 60*minutes; fprint_msg("%dm %4.1fs (frame %d @ %.2fs): ",minutes,seconds, frames,total_seconds); report_h262_picture(picture,FALSE); afd = picture->afd; } free_h262_picture(&picture); if (max > 0 && frames >= max) break; } free_h262_context(&h262); { double total_seconds = frames / (double)FRAMES_PER_SECOND; int minutes = (int)(total_seconds / 60); double seconds = total_seconds - 60*minutes; fprint_msg("Found %d MPEG-2 frame%s",frames,(frames==1?"":"s")); fprint_msg(" which is %dm %.1fs (%.2fs)\n",minutes,seconds,total_seconds); } } /* * Report on the content of an MPEG2 file * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported */ static void report_h262_items(ES_p es, int max, int verbose, int quiet) { int err; int count = 0; for (;;) { h262_item_p item; err = find_next_h262_item(es,&item); if (err == EOF) break; else if (err) { print_err("### Error finding next H.262 item\n"); break; } count++; if (!quiet) report_h262_item(item); if (verbose) print_data(TRUE," Data", item->unit.data,item->unit.data_len,10); free_h262_item(&item); if (max > 0 && count >= max) break; } fprint_msg("Found %d MPEG-2 item%s\n",count,(count==1?"":"s")); } /* * Report on the data by NAL units. */ static void report_by_nal_unit(ES_p es, int max, int quiet, int show_nal_details) { int err = 0; nal_unit_context_p context = NULL; int ref_idcs[4] = {0}; // values 0,1,2,3 int unit_types[15] = {0}; int slice_types[10] = {0}; err = build_nal_unit_context(es,&context); if (err) { print_err("### Unable to build NAL unit context to read ES\n"); return; } if (show_nal_details) set_show_nal_reading_details(context,TRUE); for (;;) { nal_unit_p nal; if (max > 0 && context->count >= max) { fprint_msg("\nStopping because %d NAL units have been read\n", context->count); break; } err = find_next_NAL_unit(context,!quiet,&nal); if (err == 2) { print_msg("... ignoring broken NAL unit\n"); continue; } else if (err) break; ref_idcs[nal->nal_ref_idc] ++; if (nal->nal_unit_type < 13) unit_types[nal->nal_unit_type] ++; else if (nal->nal_unit_type < 24) unit_types[13] ++; else unit_types[14] ++; if (nal_is_slice(nal)) slice_types[nal->u.slice.slice_type] ++; free_nal_unit(&nal); } if (err == EOF && !quiet) print_msg("EOF\n"); if (err == 0 || err == EOF) { int ii; fprint_msg("Found %d NAL unit%s\n",context->count,(context->count==1?"":"s")); print_msg("nal_ref_idc:\n"); for (ii=0; ii<4; ii++) if (ref_idcs[ii] > 0) fprint_msg(" %8d of %2d%s\n",ref_idcs[ii],ii,ii?"":" (non-reference)"); print_msg("nal_unit_type:\n"); for (ii=0; ii<13; ii++) if (unit_types[ii] > 0) fprint_msg(" %8d of type %2d (%s)\n",unit_types[ii],ii,NAL_UNIT_TYPE_STR(ii)); if (unit_types[13] > 0) fprint_msg(" %8d of type 13..23 (Reserved)\n",unit_types[13]); if (unit_types[14] > 0) fprint_msg(" %8d of typ 24..31 (Unspecified)\n",unit_types[14]); print_msg("slice_type:\n"); for (ii=0; ii<10; ii++) if (slice_types[ii] > 0) fprint_msg(" %8d of type %2d (%s)\n",slice_types[ii],ii, NAL_SLICE_TYPE_STR(ii)); } else print_err("### Abandoning reporting due to error\n"); free_nal_unit_context(&context); } /* * Report on the content of an MPEG2 file * * - `es` is the input elementary stream * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `quiet` is true, then only errors will be reported */ static void find_h264_fields(ES_p es, int max, int quiet, int verbose, int show_nal_details) { int err; int count = 0; int num_fields = 0; int num_frames = 0; access_unit_context_p context; uint32_t num_with_PTS = 0; err = build_access_unit_context(es,&context); if (err) return; if (show_nal_details) set_show_nal_reading_details(context->nac,TRUE); for (;;) { access_unit_p access_unit; // NB: remember *not* to call get_next_h264_frame! err = get_next_access_unit(context,quiet,verbose,&access_unit); if (err == EOF) break; else if (err) { print_err("### Error getting next access unit\n"); break; } count++; if (access_unit->field_pic_flag == 1) { report_access_unit(access_unit); num_fields ++; } else num_frames ++; if (access_unit_has_PTS(access_unit)) num_with_PTS ++; free_access_unit(&access_unit); if (max > 0 && count >= max) break; } fprint_msg("Found %d MPEG-4 picture%s, %d field%s, %d frame%s\n", count,(count==1?"":"s"), num_fields,(num_fields==1?"":"s"), num_frames,(num_frames==1?"":"s")); fprint_msg("Fields with PTS associated: %u\n",num_with_PTS); free_access_unit_context(&context); } /* * Report on data by access unit. */ static void report_h264_frames(ES_p es, int max, int quiet, int verbose, int show_nal_details, int count_sizes, int count_types) { int err = 0; int access_unit_count = 0; access_unit_context_p context; uint32_t min_frame_size = 1000000; uint32_t max_frame_size = 0; uint32_t sum_frame_size = 0; uint32_t num_with_PTS = 0; #define I_NON_REF 0 #define I_REF_IDR 1 #define I_REF_NON_IDR 2 #define I_OTHER 3 #define I_SLICE_I 0 #define I_SLICE_P 1 #define I_SLICE_B 2 #define I_SLICE_MIX 3 uint32_t slice_types[3][4] = {{0},{0}}; uint32_t slice_categories[4] = {0}; ES_offset start; uint32_t length; err = build_access_unit_context(es,&context); if (err) return; if (show_nal_details) set_show_nal_reading_details(context->nac,TRUE); for (;;) { access_unit_p access_unit; access_unit_count ++; err = get_next_h264_frame(context,quiet,verbose,&access_unit); if (err) break; if (!quiet) report_access_unit(access_unit); if (count_sizes) { err = get_access_unit_bounds(access_unit,&start,&length); if (err) break; if (min_frame_size > length) min_frame_size = length; if (max_frame_size < length) max_frame_size = length; sum_frame_size += length; } if (count_types && access_unit->primary_start != NULL) { if (access_unit->primary_start->nal_ref_idc == 0) { slice_categories[I_NON_REF] ++; if (all_slices_I(access_unit)) slice_types[I_NON_REF][I_SLICE_I] ++; else if (all_slices_P(access_unit)) slice_types[I_NON_REF][I_SLICE_P] ++; else if (all_slices_B(access_unit)) slice_types[I_NON_REF][I_SLICE_B] ++; else slice_types[I_NON_REF][I_SLICE_MIX] ++; } else if (access_unit->primary_start->nal_unit_type == NAL_IDR) { // Yes, I know that only I and SI frames should be allowed for IDR slice_categories[I_REF_IDR] ++; if (all_slices_I(access_unit)) slice_types[I_REF_IDR][I_SLICE_I] ++; else if (all_slices_P(access_unit)) slice_types[I_REF_IDR][I_SLICE_P] ++; else if (all_slices_B(access_unit)) slice_types[I_REF_IDR][I_SLICE_B] ++; else slice_types[I_REF_IDR][I_SLICE_MIX] ++; } else if (access_unit->primary_start->nal_unit_type == NAL_NON_IDR) { slice_categories[I_REF_NON_IDR] ++; if (all_slices_I(access_unit)) slice_types[I_REF_NON_IDR][I_SLICE_I] ++; else if (all_slices_P(access_unit)) slice_types[I_REF_NON_IDR][I_SLICE_P] ++; else if (all_slices_B(access_unit)) slice_types[I_REF_NON_IDR][I_SLICE_B] ++; else slice_types[I_REF_NON_IDR][I_SLICE_MIX] ++; } else slice_categories[I_OTHER] ++; } if (access_unit_has_PTS(access_unit)) num_with_PTS ++; free_access_unit(&access_unit); // Did the logical stream end after the last access unit? if (context->end_of_stream) { if (!quiet) print_msg("Found End-of-stream NAL unit\n"); break; } if (max > 0 && access_unit_count >= max) { fprint_msg("\nStopping because (at least) %d frames have been read\n", access_unit_count); break; } } fprint_msg("Found %d frame%s (%d NAL unit%s)\n", access_unit_count,(access_unit_count==1?"":"s"), context->nac->count,(context->nac->count==1?"":"s")); if (count_types) { if (slice_categories[I_NON_REF] > 0) { print_msg("Non-reference frames:\n"); if (slice_types[I_NON_REF][I_SLICE_I] != 0) fprint_msg(" I frames %7d\n",slice_types[I_NON_REF][I_SLICE_I]); if (slice_types[I_NON_REF][I_SLICE_P] != 0) fprint_msg(" P frames %7d\n",slice_types[I_NON_REF][I_SLICE_P]); if (slice_types[I_NON_REF][I_SLICE_B] != 0) fprint_msg(" B frames %7d\n",slice_types[I_NON_REF][I_SLICE_B]); if (slice_types[I_NON_REF][I_SLICE_MIX] != 0) fprint_msg(" Mixed/other %7d\n",slice_types[I_NON_REF][I_SLICE_MIX]); } if (slice_categories[I_REF_IDR] > 0) { print_msg("IDR frames\n"); if (slice_types[I_REF_IDR][I_SLICE_I] != 0) fprint_msg(" I frames %7d\n",slice_types[I_REF_IDR][I_SLICE_I]); if (slice_types[I_REF_IDR][I_SLICE_P] != 0) fprint_msg(" P frames %7d\n",slice_types[I_REF_IDR][I_SLICE_P]); if (slice_types[I_REF_IDR][I_SLICE_B] != 0) fprint_msg(" B frames %7d\n",slice_types[I_REF_IDR][I_SLICE_B]); if (slice_types[I_REF_IDR][I_SLICE_MIX] != 0) fprint_msg(" Mixed/other %7d\n",slice_types[I_REF_IDR][I_SLICE_MIX]); } if (slice_categories[I_REF_NON_IDR] > 0) { print_msg("Non-IDR reference frames:\n"); if (slice_types[I_REF_NON_IDR][I_SLICE_I] != 0) fprint_msg(" I frames %7d\n",slice_types[I_REF_NON_IDR][I_SLICE_I]); if (slice_types[I_REF_NON_IDR][I_SLICE_P] != 0) fprint_msg(" P frames %7d\n",slice_types[I_REF_NON_IDR][I_SLICE_P]); if (slice_types[I_REF_NON_IDR][I_SLICE_B] != 0) fprint_msg(" B frames %7d\n",slice_types[I_REF_NON_IDR][I_SLICE_B]); if (slice_types[I_REF_NON_IDR][I_SLICE_MIX] != 0) fprint_msg(" Mixed/other %7d\n",slice_types[I_REF_NON_IDR][I_SLICE_MIX]); } if (slice_categories[I_OTHER] > 0) fprint_msg("Other frame types: %d\n",slice_categories[I_OTHER]); } { double total_seconds = access_unit_count / (double)FRAMES_PER_SECOND; int minutes = (int)(total_seconds / 60); double seconds = total_seconds - 60*minutes; fprint_msg("At 25 frames/second, that is %dm %.1fs (%.2fs)\n",minutes,seconds, total_seconds); } if (count_sizes && access_unit_count > 0) fprint_msg("Frame sizes ranged from %u to %u bytes, mean %.2f\n", min_frame_size,max_frame_size, sum_frame_size/(double)access_unit_count); fprint_msg("Frames with PTS associated: %u\n",num_with_PTS); free_access_unit_context(&context); } static void print_usage() { print_msg( "Usage: esreport [switches] []\n" "\n" ); REPORT_VERSION("esreport"); print_msg( "\n" " Report on the content of an elementary stream containing H.264\n" " (MPEG-4/AVC), H.262 (MPEG-2) or AVS video data.\n" "\n" "Files:\n" " is the Elementary Stream file (but see -stdin below)\n" "\n" "What to report:\n" " The default is to report on H.262 items, AVS frames or H.264 NAL units.\n" " Other choices are:\n" "\n" " -frames Report by frames. The default for AVS.\n" " -findfields Report on any fields in the data. Ignored for AVS.\n" " -afd Report (just) on AFD changes in H.262. Ignored for the\n" " other types of file.\n" " -es Report on ES units.\n" "\n" " Reporting on frames may be modified by:\n" "\n" " -framesize Report on the sizes of frames (mean, etc.).\n" " -frametype Report on the numbers of different type of frame.\n" "\n" " (in fact, both of these imply -frame).\n" "\n" "Other switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -verbose, -v For H.262 data, output information about the data\n" " in each MPEG-2 item. For ES units, output information\n" " about the data in each ES unit. Ignored for H.264 data.\n" " -quiet, -q Only output summary information (i.e., the number\n" " of entities in the file, statistics, etc.)\n" " -x Show details of each NAL unit as it is read.\n" " -stdin Take input from , instead of a named file\n" " -max , -m Maximum number of NAL units/MPEG-2 items/AVS frames/ES units\n" " to read. If -frames, then the program will stop after\n" " that many frames. If reading 'frames', MPEG-2 and AVS will\n" " also count sequence headers and sequence end.\n" " -pes, -ts The input file is TS or PS, to be read via the\n" " PES->ES reading mechanisms\n" " -pesreport Report on PES headers. Implies -pes and -q.\n" "\n" "Stream type:\n" " If input is from a file, then the program will look at the start of\n" " the file to determine if the stream is H.264, H.262 or AVS data. This\n" " process may occasionally come to the wrong conclusion, in which case\n" " the user can override the choice using the following switches.\n" "\n" " If input is from standard input (via -stdin), then it is not possible\n" " for the program to make its own decision on the input stream type.\n" " Instead, it defaults to H.262, and relies on the user indicating if\n" " this is wrong.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" " -avs Force the program to treat the input as AVS.\n" ); } int main(int argc, char **argv) { char *input_name = NULL; int had_input_name = FALSE; int use_stdin = FALSE; int err = 0; ES_p es = NULL; int max = 0; int by_frame = FALSE; int find_fields = FALSE; int quiet = FALSE; int verbose = FALSE; int show_nal_details = FALSE; int give_pes_info = FALSE; int report_afds = FALSE; int report_framesize = FALSE; int report_frametype = FALSE; int report_pes_headers = FALSE; int report_ES = FALSE; int ii = 1; int use_pes = FALSE; int want_data = VIDEO_H262; int is_data; int force_stream_type = FALSE; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-err",argv[ii])) { CHECKARG("esreport",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### esreport: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-avc",argv[ii]) || !strcmp("-h264",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H264; } else if (!strcmp("-h262",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H262; } else if (!strcmp("-avs",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_AVS; } else if (!strcmp("-es",argv[ii])) { report_ES = TRUE; } else if (!strcmp("-frames",argv[ii])) by_frame = TRUE; else if (!strcmp("-framesize",argv[ii])) { by_frame = TRUE; report_framesize = TRUE; } else if (!strcmp("-frametype",argv[ii])) { by_frame = TRUE; report_frametype = TRUE; } else if (!strcmp("-afd",argv[ii]) || !strcmp("-afds",argv[ii])) report_afds = TRUE; else if (!strcmp("-findfields",argv[ii])) find_fields = TRUE; else if (!strcmp("-stdin",argv[ii])) { had_input_name = TRUE; // more or less use_stdin = TRUE; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { quiet = TRUE; } else if (!strcmp("-x",argv[ii])) { show_nal_details = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("esreport",ii); err = int_value("esreport",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-pes",argv[ii]) || !strcmp("-ts",argv[ii])) use_pes = TRUE; else if (!strcmp("-pesreport",argv[ii])) { report_pes_headers = TRUE; use_pes = TRUE; quiet = TRUE; } else if (!strcmp("-pesinfo",argv[ii])) { give_pes_info = TRUE; use_pes = TRUE; } else { fprint_err("### esreport: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name) { fprint_err("### esreport: Unexpected '%s'\n",argv[ii]); return 1; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### esreport: No input file specified\n"); return 1; } err = open_input_as_ES((use_stdin?NULL:input_name),use_pes,quiet, force_stream_type,want_data,&is_data,&es); if (err) { print_err("### esreport: Error opening input file\n"); return 1; } if (report_pes_headers) { es->reader->debug_read_packets = TRUE; } if (give_pes_info) { es->reader->give_info = TRUE; } if (report_ES) { report_ES_units(es,max,verbose,quiet); } else if (is_data == VIDEO_H262) { if (find_fields) find_h262_fields(es,max,verbose); else if (by_frame) report_h262_frames(es,max,verbose,quiet,report_framesize); else if (report_afds) report_h262_afds(es,max,verbose,quiet); else report_h262_items(es,max,verbose,quiet); } else if (is_data == VIDEO_AVS) { report_avs_frames(es,max,verbose,quiet,report_framesize); } else if (is_data == VIDEO_H264) { if (find_fields) find_h264_fields(es,max,quiet,verbose,show_nal_details); else if (by_frame) report_h264_frames(es,max,quiet,verbose,show_nal_details, report_framesize,report_frametype); else report_by_nal_unit(es,max,quiet,show_nal_details); } else { print_err("### esreport: Unexpected type of video data\n"); return 1; } err = close_input_as_ES(input_name,&es); if (err) { print_err("### esreport: Error closing input file\n"); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/esreverse.c000066400000000000000000000563201261471605300170450ustar00rootroot00000000000000/* * Output a reversed representation of an H.264 (MPEG-4/AVC) or H.262 (MPEG-2) * elementary stream. * * Note that the input stream must be seekable, which means that an option * to read from standard input is not provided. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "es_fns.h" #include "nalunit_fns.h" #include "accessunit_fns.h" #include "h262_fns.h" #include "ts_fns.h" #include "tswrite_fns.h" #include "pes_fns.h" #include "reverse_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" #define DEBUG 0 #define SHOW_REVERSE_DATA 1 #if SHOW_REVERSE_DATA static int show_reverse_data = FALSE; #endif /* * Write out packet data as ES or TS. This is defined in reverse.c, but * otherwise unadvertised. */ extern int write_packet_data(WRITER output, int as_TS, byte data[], int data_len, uint32_t pid, byte stream_id); /* * Find the I slices in our input stream, and output them in reverse order. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `frequency` is non-zero, then attempt to produce the effect of * keeping every th picture (similar to reversing at a * multiplication factor of `frequency`) If 0, just retain all I pictures. * - if `as_TS` is true, then output as TS packets, not ES * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if all went well, 1 if something went wrong. */ static int reverse_h262(ES_p es, WRITER output, int max, int frequency, int as_TS, int verbose, int quiet) { int err = 0; reverse_data_p reverse_data = NULL; h262_context_p hcontext = NULL; err = build_h262_context(es,&hcontext); if (err) return 1; err = build_reverse_data(&reverse_data,FALSE); if (err) { free_h262_context(&hcontext); return 1; } if (!quiet) print_msg("\nScanning forwards\n"); add_h262_reverse_context(hcontext,reverse_data); err = collect_reverse_h262(hcontext,max,verbose,quiet); if (err && err != EOF) { if (reverse_data->length > 0) { fprint_err("!!! Collected %d pictures and sequence headers," " continuing to reverse\n",reverse_data->length); } else { free_reverse_data(&reverse_data); free_h262_context(&hcontext); return 1; } } #if SHOW_REVERSE_DATA if (show_reverse_data) { int ii; for (ii=0; iilength; ii++) if (reverse_data->seq_offset[ii]) fprint_msg("%3d: %4d at " OFFSET_T_FORMAT "/%d for %d\n", ii,reverse_data->index[ii], reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); else fprint_msg("%3d: seqh at " OFFSET_T_FORMAT "/%d for %d\n", ii, reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); } if (!es->reading_ES) write_program_data(es->reader,output.ts_output); #endif if (!es->reading_ES) { // Just in case (it can't hurt) stop_server_output(es->reader); // But this is important set_PES_reader_video_only(es->reader,TRUE); } if (!quiet) print_msg("\nOutputting in reverse order\n"); if (as_TS) err = output_in_reverse_as_TS(es,output.ts_output,frequency,verbose,quiet, -1,0,reverse_data); else err = output_in_reverse_as_ES(es,output.es_output,frequency,verbose,quiet, -1,0,reverse_data); if (!err && !quiet) { uint32_t final_index = reverse_data->index[reverse_data->first_written]; print_msg("\n"); print_msg("Summary\n"); print_msg("=======\n"); print_msg(" Considered Used Written\n"); fprint_msg("Pictures %10d %10d (%4.1f%%) %10d (%4.1f%%)\n", final_index,reverse_data->pictures_kept, 100*(((double)reverse_data->pictures_kept)/final_index), reverse_data->pictures_written, 100*(((double)reverse_data->pictures_written)/final_index)); if (frequency != 0) fprint_msg("Target (pictures) . %10d (%4.1f%%) at requested" " frequency %d\n",final_index/frequency,100.0/frequency, frequency); } free_reverse_data(&reverse_data); free_h262_context(&hcontext); return err; } /* * Output any sequence and picture parameter sets * * Returns 0 if all went well, 1 if something went wrong. */ static int output_parameter_sets(WRITER output, access_unit_context_p context, int as_TS, int quiet) { nal_unit_context_p nac = context->nac; param_dict_p seq_param_dict = nac->seq_param_dict; param_dict_p pic_param_dict = nac->pic_param_dict; int ii; int err; for (ii = 0; ii < seq_param_dict->length; ii++) { ES_offset posn = seq_param_dict->posns[ii]; uint32_t length = seq_param_dict->data_lens[ii]; byte *data = NULL; if (!quiet) fprint_msg("Writing out sequence parameter set %d\n", seq_param_dict->ids[ii]); err = read_ES_data(nac->es,posn,length,NULL,&data); if (err) { fprint_err("### Error reading (sequence parameter set %d) data" " from " OFFSET_T_FORMAT "/%d for %d\n", seq_param_dict->ids[ii],posn.infile,posn.inpacket,length); return 1; } err = write_packet_data(output,as_TS,data,length,DEFAULT_VIDEO_PID, DEFAULT_VIDEO_STREAM_ID); free(data); if (err) { fprint_err("### Error writing out (sequence parameter set %d)" "data\n",seq_param_dict->ids[ii]); return 1; } } for (ii = 0; ii < pic_param_dict->length; ii++) { ES_offset posn = pic_param_dict->posns[ii]; uint32_t length = pic_param_dict->data_lens[ii]; byte *data = NULL; if (!quiet) fprint_msg("Writing out picture parameter set %d\n", pic_param_dict->ids[ii]); err = read_ES_data(nac->es,posn,length,NULL,&data); if (err) { fprint_err("### Error reading (picture parameter set %d) data" " from " OFFSET_T_FORMAT "/%d for %d\n", pic_param_dict->ids[ii],posn.infile,posn.inpacket,length); return 1; } err = write_packet_data(output,as_TS,data,length,DEFAULT_VIDEO_PID, DEFAULT_VIDEO_STREAM_ID); free(data); if (err) { fprint_err("### Error writing out (picture parameter set %d)" "data\n",pic_param_dict->ids[ii]); return 1; } } return 0; } /* * Find IDR and I access units, and output them in reverse order. * * Returns 0 if all went well, 1 if something went wrong. */ static int reverse_access_units(ES_p es, WRITER output, int max, int frequency, int as_TS, int verbose, int quiet) { int err = 0; reverse_data_p reverse_data = NULL; access_unit_context_p acontext = NULL; err = build_access_unit_context(es,&acontext); if (err) return 1; err = build_reverse_data(&reverse_data,TRUE); if (err) { free_access_unit_context(&acontext); return 1; } if (!quiet) print_msg("\nScanning forwards\n"); add_access_unit_reverse_context(acontext,reverse_data); err = collect_reverse_access_units(acontext,max,verbose,quiet); if (err && err != EOF) { if (reverse_data->length > 0) { fprint_err("!!! Collected %d access units," " continuing to reverse\n",reverse_data->length); } else { free_reverse_data(&reverse_data); free_access_unit_context(&acontext); return 1; } } #if SHOW_REVERSE_DATA if (show_reverse_data) { int ii; for (ii=0; iilength; ii++) fprint_msg("%3d: %4d at " OFFSET_T_FORMAT "/%d for %d\n", ii,reverse_data->index[ii], reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); } //if (!es->reading_ES) // write_program_data(es->reader,output.ts_output); #endif if (!es->reading_ES) { // Just in case (it can't hurt) stop_server_output(es->reader); // But this is important set_PES_reader_video_only(es->reader,TRUE); } // Before outputting any reverse data, it's a good idea to write out the // picture parameter set(s) and sequence parameter set(s) if (!quiet) print_msg("\nPreparing to output reverse data\n"); err = output_parameter_sets(output,acontext,as_TS,quiet); if (err) { free_reverse_data(&reverse_data); free_access_unit_context(&acontext); return 1; } if (!quiet) print_msg("\nOutputting in reverse order\n"); if (as_TS) err = output_in_reverse_as_TS(es,output.ts_output,frequency,verbose,quiet, -1,0,reverse_data); else err = output_in_reverse_as_ES(es,output.es_output,frequency,verbose,quiet, -1,0,reverse_data); if (!err && !quiet) { uint32_t final_index = reverse_data->index[reverse_data->first_written]; print_msg("\n"); print_msg("Summary\n"); print_msg("=======\n"); print_msg(" Considered Used Written\n"); fprint_msg("Access units %10d %10d (%4.1f%%) %10d (%4.1f%%)\n", final_index,reverse_data->pictures_kept, 100*(((double)reverse_data->pictures_kept)/final_index), reverse_data->pictures_written, 100*(((double)reverse_data->pictures_written)/final_index)); if (frequency != 0) fprint_msg("Target (access units) . %10d (%4.1f%%) at requested" " frequency %d\n",final_index/frequency,100.0/frequency, frequency); } free_reverse_data(&reverse_data); free_access_unit_context(&acontext); return err; } static void print_usage() { print_msg( "Usage: esreverse [switches] [] []\n" "\n" ); REPORT_VERSION("esreverse"); print_msg( "\n" " Output a reversed stream derived from the input H.264 (MPEG-4/AVC)\n" " or H.262 (MPEG-2) elementary stream.\n" "\n" " If output is to an H.222 Transport Stream, then fixed values for\n" " the PMT PID (0x66) and video PID (0x68) are used.\n" "\n" "Files:\n" " is the input elementary stream.\n" " is the output stream, either an equivalent elementary\n" " stream, or an H.222 Transport Stream (but see -stdout\n" " and -host below).\n" "\n" "Switches:\n" " -verbose, -v Output additional (debugging) messages\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -quiet, -q Only output error messages\n" " -stdout Write output to , instead of a named file\n" " Forces -quiet and -err stderr.\n" " -host , -host :\n" " Writes output (over TCP/IP) to the named ,\n" " instead of to a named file. If is not\n" " specified, it defaults to 88. Implies -tsout.\n" " -max , -m Maximum number of frames to read\n" " -freq Specify the frequency of frames to try to keep\n" " when reversing. Defaults to 8.\n" " -tsout Output H.222 Transport Stream\n" "\n" " -pes, -ts The input file is TS or PS, to be read via the\n" " PES->ES reading mechanisms\n" " -server Also output as normal forward video as reversal\n" " data is being collected. Implies -pes and -tsout.\n" #if SHOW_REVERSE_DATA "\n" " -x Temporary extra debugging information\n" #endif "\n" "Stream type:\n" " If input is from a file, then the program will look at the start of\n" " the file to determine if the stream is H.264 or H.262 data. This\n" " process may occasionally come to the wrong conclusion, in which case\n" " the user can override the choice using the following switches.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" ); } int main(int argc, char **argv) { char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; int use_stdout = FALSE; int use_tcpip = FALSE; int port = 88; // Useful default port number int err = 0; ES_p es = NULL; WRITER output; int max = 0; int as_TS = FALSE; int frequency = 8; // The default as stated in the usage int quiet = FALSE; int verbose = FALSE; int ii = 1; int use_pes = FALSE; int use_server = FALSE; int want_data = VIDEO_H262; int is_data; int force_stream_type = FALSE; byte stream_type; if (argc < 2) { print_usage(); return 0; } output.es_output = NULL; while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-help",argv[ii]) || !strcmp("-h",argv[ii])) { print_usage(); return 0; } #if SHOW_REVERSE_DATA else if (!strcmp("-x",argv[ii])) show_reverse_data = TRUE; #endif else if (!strcmp("-avc",argv[ii]) || !strcmp("-h264",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H264; } else if (!strcmp("-h262",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H262; } else if (!strcmp("-pes",argv[ii]) || !strcmp("-ts",argv[ii])) use_pes = TRUE; else if (!strcmp("-server",argv[ii])) { use_server = TRUE; use_pes = TRUE; as_TS = TRUE; } else if (!strcmp("-tsout",argv[ii])) as_TS = TRUE; else if (!strcmp("-stdout",argv[ii])) { had_output_name = TRUE; // more or less use_stdout = TRUE; redirect_output_stderr(); } else if (!strcmp("-err",argv[ii])) { CHECKARG("esreverse",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### esreverse: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-host",argv[ii])) { CHECKARG("esreverse",ii); err = host_value("esreverse",argv[ii],argv[ii+1],&output_name,&port); if (err) return 1; had_output_name = TRUE; // more or less use_tcpip = TRUE; as_TS = TRUE; ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("esreverse",ii); err = int_value("esreverse",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-freq",argv[ii])) { CHECKARG("esreverse",ii); err = int_value("esreverse",argv[ii],argv[ii+1],TRUE,10,&frequency); if (err) return 1; ii++; } else { fprint_err("### esreverse: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name && had_output_name) { fprint_err("### esreverse: Unexpected '%s'\n",argv[ii]); return 1; } else if (had_input_name) { output_name = argv[ii]; had_output_name = TRUE; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### esreverse: No input file specified\n"); return 1; } if (!had_output_name) { print_err("### esreverse: No output file specified\n"); return 1; } // Try to stop extraneous data ending up in our output stream if (use_stdout) { verbose = FALSE; quiet = TRUE; } err = open_input_as_ES(input_name,use_pes,quiet, force_stream_type,want_data,&is_data,&es); if (err) { print_err("### esreverse: Error opening input file\n"); return 1; } if (is_data == VIDEO_H262) stream_type = MPEG2_VIDEO_STREAM_TYPE; else if (is_data == VIDEO_H264) stream_type = AVC_VIDEO_STREAM_TYPE; else { print_err("### esreverse: Unexpected type of video data\n"); return 1; } if (as_TS) { if (use_stdout) err = tswrite_open(TS_W_STDOUT,NULL,NULL,0,quiet,&(output.ts_output)); else if (use_tcpip) err = tswrite_open(TS_W_TCP,output_name,NULL,port,quiet,&(output.ts_output)); else err = tswrite_open(TS_W_FILE,output_name,NULL,0,quiet,&(output.ts_output)); if (err) { fprint_err("### esreverse: Unable to open %s\n",output_name); (void) close_input_as_ES(input_name,&es); return 1; } } else { output.es_output = fopen(output_name,"wb"); if (output.es_output == NULL) { fprint_err("### esreverse: Unable to open output file %s: %s\n", output_name,strerror(errno)); (void) close_input_as_ES(input_name,&es); return 1; } if (!quiet) fprint_msg("Writing to %s\n",output_name); } if (!quiet) { if (as_TS) print_msg("Writing as Transport Stream\n"); fprint_msg("Filtering freqency %d\n",frequency); if (max) fprint_msg("Stopping as soon after %d %s as possible\n",max, (is_data == VIDEO_H262?"MPEG2 items":"NAL units")); } if (use_pes) { #if SHOW_REVERSE_DATA if (show_reverse_data) es->reader->debug_read_packets = TRUE; #endif if (use_server) { // For testing purposes, let's try outputting video as we collect data set_server_output(es->reader,output.ts_output,FALSE,100); es->reader->debug_read_packets = TRUE; } } // If we're writing out TS data, start it off now // (we mustn't do it after our forwards-processing function, // because that itself may output some data...) if (as_TS) { if (use_pes) { if (!quiet) fprint_msg("Using transport stream id 1, PMT PID %#x, program 1 =" " PID %#x\n",DEFAULT_PMT_PID,DEFAULT_VIDEO_PID); set_PES_reader_program_data(es->reader,1,DEFAULT_PMT_PID, DEFAULT_VIDEO_PID, DEFAULT_AUDIO_PID, // not actually used DEFAULT_VIDEO_PID); // video as PCR // Note that (a) the server output will write program data for us, // and (b) for the moment, the TS writer does not allow us to set the // stream_type } else { if (!quiet) fprint_msg("Using transport stream id 1, PMT PID %#x, program 1 =" " PID %#x, stream type %#x\n",DEFAULT_PMT_PID,DEFAULT_VIDEO_PID, stream_type); err = write_TS_program_data(output.ts_output, 1,1,DEFAULT_PMT_PID,DEFAULT_VIDEO_PID, stream_type); if (err) { print_err("### esreverse: Error writing out TS program data\n"); (void) close_input_as_ES(input_name,&es); if (as_TS) (void) tswrite_close(output.ts_output,TRUE); else if (had_output_name && !use_stdout) { err = fclose(output.es_output); if (err) fprint_err("### esreverse: (Error closing output file %s: %s)\n", output_name,strerror(errno)); } return 1; } } } if (is_data == VIDEO_H262) err = reverse_h262(es,output,max,frequency,as_TS,verbose,quiet); else err = reverse_access_units(es,output,max,frequency,as_TS,verbose,quiet); if (err) { print_err("### esreverse: Error reversing input\n"); (void) close_input_as_ES(input_name,&es); if (as_TS) (void) tswrite_close(output.ts_output,TRUE); else if (had_output_name && !use_stdout) { err = fclose(output.es_output); if (err) fprint_err("### esreverse: (Error closing output file %s: %s)\n", output_name,strerror(errno)); } return 1; } // And tidy up when we're finished if (as_TS) { err = tswrite_close(output.ts_output,quiet); if (err) { fprint_err("### esreverse: Error closing output file %s", output_name); (void) close_input_as_ES(input_name,&es); return 1; } } else if (!use_stdout) { errno = 0; err = fclose(output.es_output); if (err) { fprint_err("### esreverse: Error closing output file %s: %s\n", output_name,strerror(errno)); (void) close_input_as_ES(input_name,&es); return 1; } } err = close_input_as_ES(input_name,&es); if (err) { print_err("### esreverse: Error closing input file\n"); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ethernet.c000066400000000000000000000052071261471605300166560ustar00rootroot00000000000000/* ethernet.c */ /* * Routines for taking ethernet packets apart. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Richard Watts, Kynesim * * ***** END LICENSE BLOCK ***** */ #include "ethernet.h" #include #include "misc_fns.h" int ethernet_packet_from_pcap(pcaprec_hdr_t *hdr, const uint8_t *data, const uint32_t len, ethernet_packet_t *pkt, uint32_t *out_st, uint32_t *out_len) { uint32_t eoh; const uint8_t *p = data; const uint8_t * const eop = data + len; pkt->vlan_count = 0; // 14 bytes of src,dest,type .. if (len < 14) { return ETHERNET_ERR_PKT_TOO_SHORT; } // PCap doesn't store CRCs - it stores [dst] [src] [type] memcpy(pkt->dst_addr, p, 6); p += 6; memcpy(pkt->src_addr, p, 6); p += 6; // Type/Length is big-endian. pkt->typeorlen = uint_16_be(p); p += 2; // 0x5DC is the maximum frame length in IEEE 802.3 - anything // above that here is a type. // // Length is just the data length. if (pkt->typeorlen <= 0x5DC) { (*out_len) = pkt->typeorlen; eoh = 14; } else { // Look for VLAN while (pkt->typeorlen == 0x8100) { if (pkt->vlan_count >= ETHERNET_VLANS_MAX) { return ETHERNET_ERR_TOO_MANY_VLANS; } if (p + 4 > eop) { return ETHERNET_ERR_PKT_TOO_SHORT; } pkt->vlans[pkt->vlan_count].pcp = (p[0] >> 5) & 7; pkt->vlans[pkt->vlan_count].cfi = (p[0] >> 4) & 1; pkt->vlans[pkt->vlan_count].vid = uint_16_be(p) & 0xfff; p += 2; ++pkt->vlan_count; pkt->typeorlen = uint_16_be(p); p += 2; } eoh = p - data; // pcap doesn't store the checksum or pad .. (*out_len) = len - eoh; } (*out_st) = eoh; return 0; } /* End file */ tstools-1.13~git20151030/ethernet.h000066400000000000000000000042551261471605300166650ustar00rootroot00000000000000/* ethernet.h */ /* * Routines for taking ethernet packets apart. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Richard Watts, Kynesim * * ***** END LICENSE BLOCK ***** */ #ifndef _ethernet_h #define _ethernet_h #include #include #include "compat.h" #include "pcap.h" typedef struct ethernet_vlan_info_s { char cfi; char pcp; int vid; } ethernet_vlan_info_t; typedef struct ethernet_packet_s { // Source address. uint8_t src_addr[6]; // Destination ddress. uint8_t dst_addr[6]; // 0x5DC is the maximum frame length in 802.3 #define ETHERNET_MAY_BE_IP(typeorlen) ((typeorlen) == 0x800 || (typeorlen) <= 0x5DC) #define ETHERNET_TYPE_IP 0x800 // Type uint16_t typeorlen; #define ETHERNET_VLANS_MAX 2 int vlan_count; ethernet_vlan_info_t vlans[ETHERNET_VLANS_MAX]; // Checksum if present. Note that pcap doesn't include checksums. uint32_t checksum; } ethernet_packet_t; #define ETHERNET_ERR_PKT_TOO_SHORT (-1) #define ETHERNET_ERR_TOO_MANY_VLANS (-2) /* * \param out_st OUT Index into data at which ethernet payload starts. * \param out_len OUT Length of the ethernet payload. * \return 0 on success, -1 on failure. * */ int ethernet_packet_from_pcap(pcaprec_hdr_t *hdr, const uint8_t *data, const uint32_t len, ethernet_packet_t *pkt, uint32_t *out_st, uint32_t *out_len); #endif /* End file */ tstools-1.13~git20151030/filter.c000066400000000000000000000664221261471605300163330ustar00rootroot00000000000000/* * Support for "filtering" ES, outputting to either ES or TS. * * This provides the ability to "fast forward" through ES data. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "es_fns.h" #include "ts_fns.h" #include "accessunit_fns.h" #include "h262_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "filter_fns.h" #define DEBUG 0 // ============================================================ // Managing H.262 filter contexts // ============================================================ /* * Build a new H.262 (MPEG-2 and also MPEG-1) filter context * * Returns 0 if all goes well, 1 if something goes wrong */ static int new_h262_filter_context(h262_filter_context_p *fcontext) { h262_filter_context_p new = malloc(SIZEOF_H262_FILTER_CONTEXT); if (new == NULL) { print_err("### Unable to allocate H.262 filter context\n"); return 1; } new->h262 = NULL; new->last_seq_hdr = NULL; new->new_seq_hdr = FALSE; reset_h262_filter_context(new); *fcontext = new; return 0; } /* * Build a new filter context for "stripping" H.262 data * * - `fcontext` is the new filter context * - `h262` is the H.262 stream to read from * - `all_IP` is true if the software should keep all I and P pictures * * Returns 0 if all goes well, 1 if something goes wrong */ extern int build_h262_filter_context_strip(h262_filter_context_p *fcontext, h262_context_p h262, int all_IP) { int err = new_h262_filter_context(fcontext); if (err) return 1; (*fcontext)->h262 = h262; (*fcontext)->filter = FALSE; (*fcontext)->allref = all_IP; return 0; } /* * Build a new filter context for "filtering" H.262 data * * - `fcontext` is the new filter context * - `h262` is the H.262 stream to read from * - `freq` is the desired speed-up, or the frequency at which frames * should (ideally) be kept * * Returns 0 if all goes well, 1 if something goes wrong */ extern int build_h262_filter_context(h262_filter_context_p *fcontext, h262_context_p h262, int freq) { int err = new_h262_filter_context(fcontext); if (err) return 1; (*fcontext)->h262 = h262; (*fcontext)->filter = TRUE; (*fcontext)->freq = freq; return 0; } /* * Reset an H.262 filter context, ready to start filtering anew. */ extern void reset_h262_filter_context(h262_filter_context_p fcontext) { fcontext->pending_EOF = FALSE; fcontext->last_was_slice = FALSE; fcontext->had_previous_picture = FALSE; if (fcontext->last_seq_hdr != NULL) free_h262_picture(&fcontext->last_seq_hdr); fcontext->new_seq_hdr = FALSE; fcontext->count = 0; fcontext->frames_seen = 0; fcontext->frames_written = 0; } /* * Free a filter context * * NOTE that this does *not* free the H.262 datastructure to which the * filter context refers. * * - `fcontext` is the filter context, which will be freed, and returned * as NULL. */ extern void free_h262_filter_context(h262_filter_context_p *fcontext) { if ((*fcontext) == NULL) return; // It's a little wasteful to call this, but on the other hand it is // guaranteed to free everything we want freeing reset_h262_filter_context(*fcontext); // Just lose our reference to the H.262 datastructure, don't free it (*fcontext)->h262 = NULL; free(*fcontext); *fcontext = NULL; return; } // ============================================================ // Managing H.264 filter contexts // ============================================================ /* * Build a new H.264 filter context * * Returns 0 if all goes well, 1 if something goes wrong */ static int new_h264_filter_context(h264_filter_context_p *fcontext) { h264_filter_context_p new = malloc(SIZEOF_H264_FILTER_CONTEXT); if (new == NULL) { print_err("### Unable to allocate H.264 filter context\n"); return 1; } // Unset the important things - things we might otherwise try to free // (or, for new->es, things that stop us doing anything until we're // setup properly by the user) new->access_unit_context = NULL; reset_h264_filter_context(new); *fcontext = new; return 0; } /* * Build a new filter context for "stripping" ES data * * - `fcontext` is the new filter context * - `access` is the access unit context to read from * - `allref` is true if the software should keep all reference pictures * (H.264) or all I and P pictures (H.264) * * Returns 0 if all goes well, 1 if something goes wrong */ extern int build_h264_filter_context_strip(h264_filter_context_p *fcontext, access_unit_context_p access, int allref) { int err = new_h264_filter_context(fcontext); if (err) return 1; (*fcontext)->access_unit_context = access; (*fcontext)->filter = FALSE; (*fcontext)->allref = allref; return 0; } /* * Build a new filter context for "filtering" ES data * * - `fcontext` is the new filter context * - `access` is the access unit context to read from * - `freq` is the desired speed-up, or the frequency at which frames * should (ideally) be kept * * Returns 0 if all goes well, 1 if something goes wrong */ extern int build_h264_filter_context(h264_filter_context_p *fcontext, access_unit_context_p access, int freq) { int err = new_h264_filter_context(fcontext); if (err) return 1; (*fcontext)->access_unit_context = access; (*fcontext)->filter = TRUE; (*fcontext)->freq = freq; return 0; } /* * Reset an H.264 filter context, ready to start filtering anew. */ extern void reset_h264_filter_context(h264_filter_context_p fcontext) { // `skipped_ref_pic` is TRUE if we've skipped any reference pictures // since our last IDR (hmm - should it start off True or False?) fcontext->skipped_ref_pic = FALSE; // `last_accepted_was_not_IDR` is TRUE if the last frame kept (output) // was not an IDR. We set it TRUE initially so that we will decide // to output the first IDR we *do* find, regardless of the count. fcontext->last_accepted_was_not_IDR = TRUE; // And plainly we didn't have a previous access unit fcontext->had_previous_access_unit = FALSE; // Especially not an IDR fcontext->not_had_IDR = TRUE; fcontext->count = 0; fcontext->frames_seen = 0; fcontext->frames_written = 0; } /* * Free an H.264 filter context * * NOTE that this does *not* free the access unit context to which the * filter context refers. * * - `fcontext` is the filter context, which will be freed, and returned * as NULL. */ extern void free_h264_filter_context(h264_filter_context_p *fcontext) { if ((*fcontext) == NULL) return; // It's a little wasteful to call this, but on the other hand it is // guaranteed to free everything we want freeing reset_h264_filter_context(*fcontext); // Just lose our reference to the access unit context, don't free it (*fcontext)->access_unit_context = NULL; free(*fcontext); *fcontext = NULL; return; } // ============================================================ // Filtering H.262 // ============================================================ /* * Retrieve the next I (and/or, if fcontext->allref, P) frame in this H.262 ES. * * Any sequence end "pictures" will be ignored. * * Note that the ES data being read should be video-only. * * - `fcontext` is the information that tells us what to filter and how * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * - `seq_hdr` is a sequence header, i.e., that used by the next frame to * output. This will be NULL if the sequence header has not changed since * the last call of this function. * * Note that the caller should *not* free this, and that it will not be * maintained over calls of this function (i.e., it is a reference to a * value within the `fcontext` which is altered by this function). * * - `frame` is the next frame to output. * * Note that it is the caller's responsibility to free this with * `free_h262_picture()`. * * If an error or EOF is returned, this value is undefined. * * - `frames_seen` is the number of I and P frames (start code 0) * found by this call of the function, including the item returned * if appropriate. * * Returns 0 if it succeeds, EOF if end-of-file is read (or the last call * returned a sequence end item), 1 if some error occurs. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int get_next_stripped_h262_frame(h262_filter_context_p fcontext, int verbose, int quiet, h262_picture_p *seq_hdr, h262_picture_p *frame, int *frames_seen) { int err; // A picture is built up from several items - we start with none in hand h262_picture_p this_picture = NULL; *frames_seen = 0; if (fcontext->filter) { print_err("### Calling get_next_stripped_h262_frame with a context" " set for filtering\n"); return 1; } // Otherwise, look for something we want to keep for (;;) { if (es_command_changed(fcontext->h262->es)) { *frame = *seq_hdr = NULL; return COMMAND_RETURN_CODE; } err = get_next_h262_frame(fcontext->h262,verbose,quiet,&this_picture); if (err == EOF) { *frame = *seq_hdr = NULL; return err; } else if (err) { print_err("### Error filtering H.262 frames\n"); return 1; } // Now to stripping if (this_picture->is_picture) { (*frames_seen) ++; if ((this_picture->picture_coding_type == 1) || (this_picture->picture_coding_type == 2 && fcontext->allref) ) { *frame = this_picture; if (fcontext->new_seq_hdr) *seq_hdr = fcontext->last_seq_hdr; else *seq_hdr = NULL; fcontext->new_seq_hdr = FALSE; if (verbose) fprint_msg(">> %s picture \n", (this_picture->picture_coding_type==1?"I":"P")); return 0; } else free_h262_picture(&this_picture); } else if (this_picture->is_sequence_header) { // We maybe want to remember this sequence header for the next picture if (fcontext->last_seq_hdr == NULL) { fcontext->last_seq_hdr = this_picture; fcontext->new_seq_hdr = TRUE; if (verbose) print_msg(">> First sequence header\n"); } else if (!same_h262_picture(this_picture,fcontext->last_seq_hdr)) { if (verbose) print_msg(">> Different sequence header\n"); free_h262_picture(&fcontext->last_seq_hdr); fcontext->last_seq_hdr = this_picture; fcontext->new_seq_hdr = TRUE; } else { fcontext->new_seq_hdr = FALSE; if (verbose) print_msg(">> Identical sequence header\n"); free_h262_picture(&this_picture); } } } } /* * Retrieve the next I frame, from the H.262 ES, aiming for an "apparent" kept * frequency as stated. * * Any sequence end "pictures" will be ignored. * * Note that the ES data being read should be video-only. * * - `fcontext` is the information that tells us what to filter and how * (including the desired frequency) * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * - `seq_hdr` is a sequence header, i.e., that used by the next picture to * output. This will be NULL if `frame` is NULL. * * Note that the caller should *not* free this, and that it will not be * maintained over calls of this function (i.e., it is a reference to a * value within the `fcontext` which is altered by this function). * * - `frame` is the next frame to output. This will be NULL if the last frame * should be output again, to provide the requested apparent frequency. * * Note that it is the caller's responsibility to free this with * `free_h262_picture()`. * * If an error or EOF is returned, this value is undefined. * * - `frames_seen` is the number of I and P frames found by this call of * the function, including the item returned if appropriate. * * Returns 0 if it succeeds, EOF if end-of-file is read, or we've just read a * sequence end item, or the last call ended a picture on a sequence end * item, 1 if some error occurs. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int get_next_filtered_h262_frame(h262_filter_context_p fcontext, int verbose, int quiet, h262_picture_p *seq_hdr, h262_picture_p *frame, int *frames_seen) { int err; // A picture is built up from several items - we start with none in hand h262_picture_p this_picture = NULL; *frames_seen = 0; if (!fcontext->filter) { print_err("### Calling get_next_filtered_h262_frame with a context" " set for stripping\n"); return 1; } // Otherwise, look for something we want to keep for (;;) { if (es_command_changed(fcontext->h262->es)) { *frame = *seq_hdr = NULL; return COMMAND_RETURN_CODE; } // If the picture is an I picture, we want it to contain an appropriate // AFD - so ask for that fcontext->h262->add_fake_afd = TRUE; err = get_next_h262_frame(fcontext->h262,verbose,quiet,&this_picture); if (err == EOF) { *frame = *seq_hdr = NULL; fcontext->h262->add_fake_afd = FALSE; return err; } else if (err) { print_err("### Error filtering H.262 frames\n"); fcontext->h262->add_fake_afd = FALSE; return 1; } // Reinstate normal "only include actual AFDs" fcontext->h262->add_fake_afd = FALSE; // Now to filtering if (this_picture->is_picture) { fcontext->count ++; (*frames_seen) ++; fcontext->frames_seen ++; if (this_picture->picture_coding_type == 1 && fcontext->count < fcontext->freq) { // It is an I picture, but it is too soon if (verbose) { fprint_msg("+++ %d/%d DROP: Too soon\n",fcontext->count,fcontext->freq); } } else if (this_picture->picture_coding_type != 1) { // It is not an I picture if (verbose) { fprint_msg("+++ %d/%d DROP: %s picture\n",fcontext->count,fcontext->freq, H262_PICTURE_CODING_STR(this_picture->picture_coding_type)); } // But do we want to pad with (i.e., repeat) the previous I picture? if (fcontext->freq > 0) { int pictures_wanted = fcontext->frames_seen / fcontext->freq; int repeat = pictures_wanted - fcontext->frames_written; if (repeat > 0 && fcontext->had_previous_picture) { if (verbose) print_msg(">>> output last picture again\n"); free_h262_picture(&this_picture); *seq_hdr = NULL; *frame = NULL; fcontext->frames_written ++; return 0; } } } else { // It was an I picture, and not too soon if (verbose) { fprint_msg("+++ %d/%d KEEP\n",fcontext->count,fcontext->freq); } fcontext->count = 0; fcontext->had_previous_picture = TRUE; *seq_hdr = fcontext->last_seq_hdr; *frame = this_picture; fcontext->frames_written ++; return 0; } free_h262_picture(&this_picture); } else if (this_picture->is_sequence_header) { // We want to remember the sequence header for the next picture if (fcontext->last_seq_hdr != NULL) free_h262_picture(&fcontext->last_seq_hdr); fcontext->last_seq_hdr = this_picture; } } } // ============================================================ // Filtering H.264 // ============================================================ /* * Return the next IDR or I (and maybe any reference) frame from this H.264 ES. * * Note that the ES data being read should be video-only. * * - `fcontext` is the information that tells us what to filter and how * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `frame` is the next frame to output. * Note that it is the caller's responsibility to free this with * `free_access_unit()`. * If an error or EOF is returned, this value is undefined. * - `frames_seen` is the number of frames found by this call * of the function, including the frame returned. * * Returns 0 if it succeeds, EOF if end-of-file is read (or an an end of * stream NAL unit has been passed), 1 if some error occurs. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int get_next_stripped_h264_frame(h264_filter_context_p fcontext, int verbose, int quiet, access_unit_p *frame, int *frames_seen) { int err = 0; int keep = FALSE; // Should we keep the current access unit? access_unit_p this_access_unit = NULL; *frames_seen = 0; for (;;) { if (es_command_changed(fcontext->access_unit_context->nac->es)) return COMMAND_RETURN_CODE; if (verbose) print_msg("\n"); err = get_next_h264_frame(fcontext->access_unit_context,quiet,verbose, &this_access_unit); if (err == EOF) return err; else if (err) return 1; (*frames_seen) ++; if (this_access_unit->primary_start == NULL) { // We don't have a primary picture - no VCL NAL // There seems little point in keeping the access unit keep = FALSE; if (verbose) print_msg("++ DROP: no primary picture\n"); } else if (this_access_unit->primary_start->nal_ref_idc == 0) { // This is not a reference frame, so it's of no interest keep = FALSE; if (verbose) print_msg("++ DROP: not reference\n"); } else if (fcontext->allref) { // We want to keep all reference frames if (this_access_unit->primary_start->nal_unit_type == NAL_IDR || this_access_unit->primary_start->nal_unit_type == NAL_NON_IDR) { keep = TRUE; if (verbose) print_msg("++ KEEP: reference picture\n"); } else { keep = FALSE; if (verbose) print_msg("++ DROP: sequence or parameter set, etc.\n"); } } else { // We only want to keep IDR and I frames if (this_access_unit->primary_start->nal_unit_type == NAL_IDR) { keep = TRUE; if (verbose) print_msg("++ KEEP: IDR picture\n"); } else if (this_access_unit->primary_start->nal_unit_type == NAL_NON_IDR && all_slices_I(this_access_unit)) { keep = TRUE; if (verbose) print_msg("++ KEEP: all slices I\n"); } else { keep = FALSE; if (verbose) print_msg("++ DROP: not IDR or all slices I\n"); } } if (keep) { *frame = this_access_unit; return 0; } // We've no further use for this access unit free_access_unit(&this_access_unit); } } /* * Retrieve the next frame from the H.264 (MPEG-4/AVC) ES, aiming * for an "apparent" kept frequency as stated. * * Note that the ES data being read should be video-only. * * - `fcontext` is the information that tells us what to filter and how * (including the desired frequency) * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * - `frame` is the next frame to output. * * If the function succeeds and `frame` is NULL, it means that the * last frame should be output again. * * Note that it is the caller's responsibility to free this frame with * `free_access_unit()`. * * If an error or EOF is returned, this value is undefined. * * - `frames_seen` is the number of frames found by this call of the function, * including the frame returned. * * Returns 0 if all went well, 1 if something went wrong. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int get_next_filtered_h264_frame(h264_filter_context_p fcontext, int verbose, int quiet, access_unit_p *frame, int *frames_seen) { int err = 0; int keep = FALSE; // Should we keep the current access unit? access_unit_p this_access_unit = NULL; *frames_seen = 0; for (;;) { if (es_command_changed(fcontext->access_unit_context->nac->es)) return COMMAND_RETURN_CODE; if (verbose) print_msg("\n"); err = get_next_h264_frame(fcontext->access_unit_context,quiet,verbose, &this_access_unit); if (err == EOF) return err; else if (err) return 1; fcontext->count ++; (*frames_seen) ++; fcontext->frames_seen ++; if (this_access_unit->primary_start == NULL) { // We don't have a primary picture - no VCL NAL // There seems little point in keeping the access unit keep = FALSE; if (verbose) fprint_msg("++ %d/%d DROP: no primary picture\n", fcontext->count,fcontext->freq); } else if (this_access_unit->primary_start->nal_ref_idc == 0) { // This is not a reference frame, so it's of no interest keep = FALSE; if (verbose) fprint_msg("++ %d/%d DROP: not a reference frame\n", fcontext->count,fcontext->freq); } else if (this_access_unit->primary_start->nal_unit_type == NAL_IDR && fcontext->last_accepted_was_not_IDR) { // This frame is an IDR, and the last frame kept was not, so // we'll output it regardless - we don't expect to get enough // IDR pictures that this will be a problem, and they're // valuable because they're the "limit" for other frames that // refer backwards // (should we reset the count to zero? - seems sensible) keep = TRUE; fcontext->not_had_IDR = FALSE; fcontext->skipped_ref_pic = FALSE; fcontext->last_accepted_was_not_IDR = FALSE; if (verbose) fprint_msg("++ %d/%d KEEP: IDR and last was not\n", fcontext->count,fcontext->freq); } else if (this_access_unit->primary_start->nal_unit_type == NAL_IDR && fcontext->not_had_IDR) { // We haven't had an IDR yet in this filter run, so we had better // output this one as a "good start" keep = TRUE; fcontext->skipped_ref_pic = FALSE; fcontext->last_accepted_was_not_IDR = FALSE; if (verbose) fprint_msg("++ %d/%d KEEP: IDR and first IDR of filter run\n", fcontext->count,fcontext->freq); } else if (fcontext->count < fcontext->freq) { // It's too soon, so ignore it - but notice that we *have* // ignored a reference picture keep = FALSE; fcontext->skipped_ref_pic = TRUE; if (verbose) fprint_msg("++ %d/%d DROP: Too soon (skipping ref frame)\n", fcontext->count,fcontext->freq); } else if (this_access_unit->primary_start->nal_unit_type == NAL_IDR) { // It's an IDR, so output it keep = TRUE; fcontext->skipped_ref_pic = FALSE; fcontext->last_accepted_was_not_IDR = FALSE; if (verbose) fprint_msg("++ %d/%d KEEP: IDR\n",fcontext->count,fcontext->freq); } else if (all_slices_I(this_access_unit)) { // It is an I picture (either it has all of its slices // type "I", or it has a single slice which is of type "I") keep = TRUE; fcontext->last_accepted_was_not_IDR = TRUE; if (verbose) fprint_msg("++ %d/%d KEEP: I frame\n",fcontext->count,fcontext->freq); } else if (!fcontext->skipped_ref_pic && all_slices_I_or_P(this_access_unit)) { // It is a P or I&P picture, but we know that we have output all // the reference pictures since the last IDR, so it is // safe to output it keep = TRUE; fcontext->last_accepted_was_not_IDR = TRUE; if (verbose) fprint_msg("++ %d/%d KEEP: P frame. no skipped ref frames\n", fcontext->count,fcontext->freq); } else { keep = FALSE; fcontext->skipped_ref_pic = TRUE; if (verbose) fprint_msg("++ %d/%d DROP: ref frame skipped earlier\n", fcontext->count,fcontext->freq); } if (keep) { *frame = this_access_unit; fcontext->had_previous_access_unit = TRUE; fcontext->frames_written ++; fcontext->count = 0; return 0; } else { if (fcontext->freq > 0) { int access_units_wanted = fcontext->frames_seen / fcontext->freq; int repeat = access_units_wanted - fcontext->frames_written; if (repeat > 0 && fcontext->had_previous_access_unit) { if (verbose) print_msg(">>> output last access unit again\n"); free_access_unit(&this_access_unit); *frame = NULL; fcontext->frames_written ++; return 0; } } // We've no further use for this access unit free_access_unit(&this_access_unit); } } } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/filter_defns.h000066400000000000000000000102641261471605300175100ustar00rootroot00000000000000/* * Datastructures for filtering ES data ("fast forward") and writing to ES or * TS. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _filter_defns #define _filter_defns #include "compat.h" #include "es_defns.h" #include "h262_defns.h" #include "accessunit_defns.h" #include "reverse_defns.h" // Filtering comes in two varieties: // - "stripping" means retaining just reference pictures. For H.262 this // means the I pictures (and maybe the P pictures), for H.264 this means // the IDR and I pictures (or maybe all reference pictures). This is simple // to do, but the speedup resulting is very dependant on the data. // - "filtering" means attempting to keep frames as a particular frequency, // so, for instance, a frequency of 8 would mean trying to keep every 8th // frame, or a speedup of 8x. This is harder to do as it depends rather // crucially on the distribution of reference frames in the data. // ------------------------------------------------------------ struct h262_filter_context { h262_context_p h262; // The H.262 stream we are reading from int filter; // TRUE if filtering, FALSE if stripping int freq; // Frequency of frames to try to keep if filtering int allref; // Keep all I and P pictures if stripping? // (the name `allref` is used for compatibility with the H.264 filter // context - it's a little easier to have one name for both filters) // For any operation on H.262, we want: int pending_EOF; // next time a function is called, say we had EOF // When filtering, we want: int count; // a rolling count to compare with the desired frequency int last_was_slice; int had_previous_picture; h262_picture_p last_seq_hdr; // When stripping, we want: int new_seq_hdr; // has the sequence header changed? int frames_seen; // number of pictures seen this filter run int frames_written; // number of pictures written (or, returned) }; typedef struct h262_filter_context *h262_filter_context_p; #define SIZEOF_H262_FILTER_CONTEXT sizeof(struct h262_filter_context) // ------------------------------------------------------------ struct h264_filter_context { access_unit_context_p access_unit_context; // our "reader" for access units int filter; // TRUE if filtering, FALSE if stripping int freq; // Frequency of frames to try to keep if filtering int allref; // Keep all reference pictures // When filtering, we want: // a rolling count to compare with the desired frequency int count; // `skipped_ref_pic` is TRUE if we've skipped any reference pictures // since our last IDR. int skipped_ref_pic; // `last_accepted_was_not_IDR` is TRUE if the last frame kept (output) // was not an IDR. We set it TRUE initially so that we will decide // to output the first IDR we *do* find, regardless of the count. int last_accepted_was_not_IDR; int had_previous_access_unit; // Have we had an IDR in this run of the filter? int not_had_IDR; int frames_seen; // number seen this filter run int frames_written; // number written (or, returned) }; typedef struct h264_filter_context *h264_filter_context_p; #define SIZEOF_H264_FILTER_CONTEXT sizeof(struct h264_filter_context) #endif // _filter_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/filter_fns.h000066400000000000000000000250121261471605300171740ustar00rootroot00000000000000/* * Functions for filtering ES data ("fast forward") and writing to ES or TS. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _filter_fns #define _filter_fns #include "filter_defns.h" /* * Build a new filter context for "stripping" H.262 data * * - `fcontext` is the new filter context * - `h262` is the H.262 stream to read from * - `all_IP` is true if the software should keep all I and P pictures * * Returns 0 if all goes well, 1 if something goes wrong */ extern int build_h262_filter_context_strip(h262_filter_context_p *fcontext, h262_context_p h262, int all_IP); /* * Build a new filter context for "filtering" H.262 data * * - `fcontext` is the new filter context * - `h262` is the H.262 stream to read from * - `freq` is the desired speed-up, or the frequency at which frames * should (ideally) be kept * * Returns 0 if all goes well, 1 if something goes wrong */ extern int build_h262_filter_context(h262_filter_context_p *fcontext, h262_context_p h262, int freq); /* * Reset an H.262 filter context, ready to start filtering anew. */ extern void reset_h262_filter_context(h262_filter_context_p fcontext); /* * Free a filter context * * NOTE that this does *not* free the H.262 datastructure to which the * filter context refers. * * - `fcontext` is the filter context, which will be freed, and returned * as NULL. */ extern void free_h262_filter_context(h262_filter_context_p *fcontext); /* * Build a new filter context for "stripping" ES data * * - `fcontext` is the new filter context * - `access` is the access unit context to read from * - `allref` is true if the software should keep all reference pictures * (H.264) or all I and P pictures (H.264) * * Returns 0 if all goes well, 1 if something goes wrong */ extern int build_h264_filter_context_strip(h264_filter_context_p *fcontext, access_unit_context_p access, int allref); /* * Build a new filter context for "filtering" ES data * * - `fcontext` is the new filter context * - `access` is the access unit context to read from * - `freq` is the desired speed-up, or the frequency at which frames * should (ideally) be kept * * Returns 0 if all goes well, 1 if something goes wrong */ extern int build_h264_filter_context(h264_filter_context_p *fcontext, access_unit_context_p access, int freq); /* * Reset an H.264 filter context, ready to start filtering anew. */ extern void reset_h264_filter_context(h264_filter_context_p fcontext); /* * Free an H.264 filter context * * NOTE that this does *not* free the access unit context to which the * filter context refers. * * - `fcontext` is the filter context, which will be freed, and returned * as NULL. */ extern void free_h264_filter_context(h264_filter_context_p *fcontext); /* * Retrieve the next I (and/or, if fcontext->allref, P) frame in this H.262 ES. * * Any sequence end "pictures" will be ignored. * * Note that the ES data being read should be video-only. * * - `fcontext` is the information that tells us what to filter and how * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * - `seq_hdr` is a sequence header, i.e., that used by the next frame to * output. This will be NULL if the sequence header has not changed since * the last call of this function. * * Note that the caller should *not* free this, and that it will not be * maintained over calls of this function (i.e., it is a reference to a * value within the `fcontext` which is altered by this function). * * - `frame` is the next frame to output. * * Note that it is the caller's responsibility to free this with * `free_h262_picture()`. * * If an error or EOF is returned, this value is undefined. * * - `frames_seen` is the number of I and P frames (start code 0) * found by this call of the function, including the item returned * if appropriate. * * Returns 0 if it succeeds, EOF if end-of-file is read (or the last call * returned a sequence end item), 1 if some error occurs. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int get_next_stripped_h262_frame(h262_filter_context_p fcontext, int verbose, int quiet, h262_picture_p *seq_hdr, h262_picture_p *frame, int *frames_seen); /* * Retrieve the next I frame, from the H.262 ES, aiming for an "apparent" kept * frequency as stated. * * Any sequence end "pictures" will be ignored. * * Note that the ES data being read should be video-only. * * - `fcontext` is the information that tells us what to filter and how * (including the desired frequency) * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * - `seq_hdr` is a sequence header, i.e., that used by the next picture to * output. This will be NULL if `frame` is NULL. * * Note that the caller should *not* free this, and that it will not be * maintained over calls of this function (i.e., it is a reference to a * value within the `fcontext` which is altered by this function). * * - `frame` is the next frame to output. This will be NULL if the last frame * should be output again, to provide the requested apparent frequency. * * Note that it is the caller's responsibility to free this with * `free_h262_picture()`. * * If an error or EOF is returned, this value is undefined. * * - `frames_seen` is the number of I and P frames found by this call of * the function, including the item returned if appropriate. * * Returns 0 if it succeeds, EOF if end-of-file is read, or we've just read a * sequence end item, or the last call ended a picture on a sequence end * item, 1 if some error occurs. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int get_next_filtered_h262_frame(h262_filter_context_p fcontext, int verbose, int quiet, h262_picture_p *seq_hdr, h262_picture_p *frame, int *frames_seen); /* * Return the next IDR or I (and maybe any reference) frame from this H.264 ES. * * Note that the ES data being read should be video-only. * * - `fcontext` is the information that tells us what to filter and how * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `frame` is the next frame to output. * Note that it is the caller's responsibility to free this with * `free_access_unit()`. * If an error or EOF is returned, this value is undefined. * - `frames_seen` is the number of frames found by this call * of the function, including the frame returned. * * Returns 0 if it succeeds, EOF if end-of-file is read (or an an end of * stream NAL unit has been passed), 1 if some error occurs. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int get_next_stripped_h264_frame(h264_filter_context_p fcontext, int verbose, int quiet, access_unit_p *frame, int *frames_seen); /* * Retrieve the next frame from the H.264 (MPEG-4/AVC) ES, aiming * for an "apparent" kept frequency as stated. * * Note that the ES data being read should be video-only. * * - `fcontext` is the information that tells us what to filter and how * (including the desired frequency) * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * - `frame` is the next frame to output. * * If the function succeeds and `frame` is NULL, it means that the * last frame should be output again. * * Note that it is the caller's responsibility to free this frame with * `free_access_unit()`. * * If an error or EOF is returned, this value is undefined. * * - `frames_seen` is the number of frames found by this call of the function, * including the frame returned. * * Returns 0 if all went well, 1 if something went wrong. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int get_next_filtered_h264_frame(h264_filter_context_p fcontext, int verbose, int quiet, access_unit_p *frame, int *frames_seen); #endif // _filter_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/fmtx.c000066400000000000000000000065541261471605300160240ustar00rootroot00000000000000/* * Support for formatting time stamps * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include #include "compat.h" #include "fmtx.h" static TCHAR fmtx_buffers[FMTX_BUFFERS_COUNT][FMTX_BUFFER_SIZE]; static int fmtx_buf_no = 0; TCHAR *fmtx_alloc() { const int n = fmtx_buf_no++ % FMTX_BUFFERS_COUNT; return fmtx_buffers[n]; } int frac_27MHz(int64_t n) { return (int)((n < 0 ? -n : n) % 300LL); } const TCHAR *fmtx_timestamp(int64_t n, unsigned int flags) { TCHAR *buf = fmtx_alloc(); int64_t n27 = n * ((flags & FMTX_TS_N_27MHz) != 0 ? 1LL : 300LL); switch (flags & FMTX_TS_DISPLAY_MASK) { default: case FMTX_TS_DISPLAY_90kHz_RAW: _sntprintf(buf, FMTX_BUFFER_SIZE, _T("%") I64FMT _T("dt"), n27 / 300LL); break; case FMTX_TS_DISPLAY_27MHz_RAW: _sntprintf(buf, FMTX_BUFFER_SIZE, _T("%") I64FMT _T("d:%03dt"), n27 / 300LL, frac_27MHz(n27)); break; case FMTX_TS_DISPLAY_90kHz_32BIT: { int64_t n90 = n27 / 300LL; TCHAR * p = buf; if (n90 < 0) *p++ = _T('-'); _sntprintf(p, FMTX_BUFFER_SIZE, _T("%ut"), (unsigned int)(n90 < 0 ? -n90 : n90)); break; } case FMTX_TS_DISPLAY_ms: // No timestamp when converted into ms should exceed 32bits _sntprintf(buf, FMTX_BUFFER_SIZE, _T("%dms"), (int)(n27 / 27000LL)); break; case FMTX_TS_DISPLAY_HMS: { unsigned int h, m, s, f; int64_t a27 = n27 < 0 ? -n27 : n27; a27 /= I64K(27); //us f = (unsigned int)(a27 % I64K(1000000)); a27 /= I64K(1000000); s = (unsigned int)(a27 % I64K(60)); a27 /= I64K(60); m = (unsigned int)(a27 % I64K(60)); h = (unsigned int)(a27 / I64K(60)); _sntprintf(buf, FMTX_BUFFER_SIZE, _T("%s%u:%02u:%02u.%04u"), n27 < 0 ? _T("-") : _T(""), h, m, s, f/1000); break; } } return (const TCHAR *)buf; } static const struct s2tsfss { const char * str; int flags; } s2tsf[] = { {"hms", FMTX_TS_DISPLAY_HMS}, {"ms", FMTX_TS_DISPLAY_ms}, {"90", FMTX_TS_DISPLAY_90kHz_RAW}, {"32", FMTX_TS_DISPLAY_90kHz_32BIT}, {"27", FMTX_TS_DISPLAY_27MHz_RAW}, {NULL, -1} }; int fmtx_str_to_timestamp_flags(const TCHAR *arg_str) { const struct s2tsfss * p; for (p = s2tsf; p->str != NULL; ++p) { if (strcmp(p->str, arg_str) == 0) break; } return p->flags; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/fmtx.h000066400000000000000000000037121261471605300160220ustar00rootroot00000000000000/* * Support for formatting time stamps * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #define FMTX_BUFFERS_COUNT 8 #define FMTX_BUFFER_SIZE 128 #ifdef _WIN32 #include #define I64FMT _T("I64") #define I64K(x) x##I64 #else typedef char TCHAR; #define _T(x) x #define I64FMT "ll" #define I64K(x) x##LL #define _stprintf sprintf #define _sntprintf snprintf #define _tcscmp strcmp #endif // Flags to fmtx_time_stamp #define FMTX_TS_N_90kHz 0 // Supplied time stamp is in 90kHz units #define FMTX_TS_N_27MHz 1 // Supplied time stamp is in 27Mhz units #define FMTX_TS_DISPLAY_MASK 0xff0 #define FMTX_TS_DISPLAY_90kHz_RAW 0 #define FMTX_TS_DISPLAY_90kHz_32BIT 0x10 #define FMTX_TS_DISPLAY_27MHz_RAW 0x20 #define FMTX_TS_DISPLAY_ms 0x30 #define FMTX_TS_DISPLAY_HMS 0x40 const TCHAR * fmtx_timestamp(int64_t n, unsigned int flags); int fmtx_str_to_timestamp_flags(const TCHAR * arg_str); // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/h222.c000066400000000000000000000062611261471605300155160ustar00rootroot00000000000000/* * Datastructures and definitions useful for working with H.222 data, * whether it be Transport Stream or Program Stream * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include "h222_fns.h" extern const char *h222_stream_type_str(unsigned s) { switch (s) { case 0x00: return "Reserved"; case 0x01: return "11172-2 video (MPEG-1)"; case 0x02: return "H.262/13818-2 video (MPEG-2) or 11172-2 constrained video"; case 0x03: return "11172-3 audio (MPEG-1)"; case 0x04: return "13818-3 audio (MPEG-2)"; case 0x05: return "H.222.0/13818-1 private sections"; case 0x06: return "H.222.0/13818-1 PES private data (maybe Dolby/AC-3 in DVB)"; case 0x07: return "13522 MHEG"; case 0x08: return "H.222.0/13818-1 Annex A - DSM CC"; case 0x09: return "H.222.1"; case 0x0A: return "13818-6 type A"; case 0x0B: return "13818-6 type B"; case 0x0C: return "13818-6 type C"; case 0x0D: return "13818-6 type D"; case 0x0E: return "H.222.0/13818-1 auxiliary"; case 0x0F: return "13818-7 Audio with ADTS transport syntax"; case 0x10: return "14496-2 Visual (MPEG-4 part 2 video)"; case 0x11: return "14496-3 Audio with LATM transport syntax (14496-3/AMD 1)"; case 0x12: return "14496-1 SL-packetized or FlexMux stream in PES packets"; case 0x13: return "14496-1 SL-packetized or FlexMux stream in 14496 sections"; case 0x14: return "ISO/IEC 13818-6 Synchronized Download Protocol"; case 0x15: return "Metadata in PES packets"; case 0x16: return "Metadata in metadata_sections"; case 0x17: return "Metadata in 13818-6 Data Carousel"; case 0x18: return "Metadata in 13818-6 Object Carousel"; case 0x19: return "Metadata in 13818-6 Synchronized Download Protocol"; case 0x1A: return "13818-11 MPEG-2 IPMP stream"; case 0x1B: return "H.264/14496-10 video (MPEG-4/AVC)"; case 0x24: return "HEVC video stream"; case 0x25: return "HEVC temporal video subset (profile Annex A H.265)"; case 0x42: return "AVS Video"; case 0x7F: return "IPMP stream"; case 0x81: return "User private (commonly Dolby/AC-3 in ATSC)"; default: if ((0x1C < s) && (s < 0x7E)) return "H.220.0/13818-1 reserved"; else if ((0x80 <= s) && (s <= 0xFF)) return "User private"; return "Unrecognised"; } } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/h222_defns.h000066400000000000000000000226371261471605300167070ustar00rootroot00000000000000/* * Datastructures and definitions useful for working with H.222 data, * whether it be Transport Stream or Program Stream * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _h222_defns #define _h222_defns // Include our function definition(s) // -- this is actually just the function for returning a string // representing a stream type (according to the following table), // which *used* to be a macro, defined in this header file. #include "h222_fns.h" // ------------------------------------------------------------ // H.222.0 Table 2-29: Stream type assignments, as amended by // H.222.0 (2000) Amendment 3 // // Value Description // ===== ============================ // 00 ITU-T | ISO/IEC Reserved // 01 ISO/IEC 11172-2 Video // 02 ITU-T Rec. H.262 | ISO/IEC 13818-2 Video or ISO/IEC 11172-2 // constrained parameter video stream // 03 ISO/IEC 11172-3 Audio // 04 ISO/IEC 13818-3 Audio // 05 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 private_sections // 06 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 PES packets containing // private data -- traditionally DVB Dolby (AC-3) // 07 ISO/IEC 13522 MHEG // 08 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A DSM CC // 09 ITU-T Rec. H.222.1 // 0A ISO/IEC 13818-6 type A // 0B ISO/IEC 13818-6 type B // 0C ISO/IEC 13818-6 type C // 0D ISO/IEC 13818-6 type D // 0E ITU-T Rec. H.222.0 | ISO/IEC 13818-1 auxiliary // 0F ISO/IEC 13818-7 Audio with ADTS transport syntax // 10 ISO/IEC 14496-2 Visual // 11 ISO/IEC 14496-3 Audio with the LATM transport syntax as defined // in ISO/IEC 14496-3 / AMD 1 // 12 ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried // in PES packets // 13 ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried // in ISO/IEC14496_sections. // 14 ISO/IEC 13818-6 Synchronized Download Protocol // 15 Metadata carried in PES packets // 16 Metadata carried in metadata_sections // 17 Metadata carried in ISO/IEC 13818-6 Data Carousel // 18 Metadata carried in ISO/IEC 13818-6 Object Carousel // 19 Metadata carried in ISO/IEC 13818-6 Synchronized Download Protocol // 1A IPMP stream (defined in ISO/IEC 13818-11, MPEG-2 IPMP) // 1B AVC video stream as defined in ITU-T Rec. H.264 | ISO/IEC 14496-10 // Video // 1C-7E ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Reserved // 7F IPMP stream // 80-FF User Private // 81 Traditionally ATSC Dolby (AC-3) #define MPEG1_VIDEO_STREAM_TYPE 0x01 #define MPEG2_VIDEO_STREAM_TYPE 0x02 // H.262 #define AVC_VIDEO_STREAM_TYPE 0x1B // MPEG-4 part10 - H.264 #define AVS_VIDEO_STREAM_TYPE 0x42 // AVS -- Chinese standard #define DVB_DOLBY_AUDIO_STREAM_TYPE 0x06 // [1] #define ATSC_DOLBY_AUDIO_STREAM_TYPE 0x81 // [1] #define MPEG2_AUDIO_STREAM_TYPE 0x04 #define MPEG1_AUDIO_STREAM_TYPE 0x03 #define ADTS_AUDIO_STREAM_TYPE 0x0F // AAC ADTS #define MPEG4_PART2_VIDEO_STREAM_TYPE 0x10 #define LATM_AUDIO_STREAM_TYPE 0x11 // How much do we support this? #define H265_VIDEO_STREAM_TYPE 0x24 #define DOLBY_DVB_STREAM_TYPE 0x06 // [1] #define DOLBY_ATSC_STREAM_TYPE 0x81 // [1] // [1] In DVB (the European transmission standard) Dolby (AC-3) audio is // carried in stream type 0x06, but in ATSC (the USA standard), stream // type 0x81 is used. Note that both of these are essentially just saying // that the data is a private stream, so technically one needs to set // descriptors in the PMT as well to say we really mean Dolby (AC-3) // Also, in DVB, other types of stream can be in 0x06. #define IS_VIDEO_STREAM_TYPE(s) ((s)==MPEG1_VIDEO_STREAM_TYPE || \ (s)==MPEG2_VIDEO_STREAM_TYPE || \ (s)==AVC_VIDEO_STREAM_TYPE || \ (s)==H265_VIDEO_STREAM_TYPE || \ (s)==AVS_VIDEO_STREAM_TYPE || \ (s)==MPEG4_PART2_VIDEO_STREAM_TYPE) // Although I include Dolby in the "standard" audio types, beware that the // stream type usage is not specified by H.222 itself - it is "convention" // (albeit a standardised convention) how private streams are used to transmit // Dolby. There is a case to be made that, at any one time, we should not // recognise *both* potential Dolby stream types, but just one or the other // (see [1] above) according to the standard the user is expecting. On the // other hand, practice seems to be to use the stream types only in the // expected manner. #define IS_DOLBY_STREAM_TYPE(s) ((s)==DOLBY_DVB_STREAM_TYPE || \ (s)==DOLBY_ATSC_STREAM_TYPE) #define IS_AUDIO_STREAM_TYPE(s) ((s)==MPEG1_AUDIO_STREAM_TYPE || \ (s)==MPEG2_AUDIO_STREAM_TYPE || \ (s)==ADTS_AUDIO_STREAM_TYPE || \ (s)==LATM_AUDIO_STREAM_TYPE || \ IS_DOLBY_STREAM_TYPE((s))) // ------------------------------------------------------------ // Stream ids, as used in PES headers // H.222.0 Table 2-18: Stream_id assignments, as amended by // H.222.0 (2000) Amendment 3 // // Note Hex stream_id stream coding // ==== === ========= ============= // 1 BC 1011 1100 program_stream_map // 2 BD 1011 1101 private_stream_1 // BE 1011 1110 padding_stream // 3 BF 1011 1111 private_stream_2 // C0-DF 110x xxxx ISO/IEC 13818-3 or ISO/IEC 11172-3 or // ISO/IEC 13818-7 or ISO/IEC 14496-3 audio stream // number x xxxx // Ex 1110 xxxx ITU-T Rec. H.262 | ISO/IEC 13818-2, ISO/IEC 11172-2, // ISO/IEC 14496-2 or ITU-T Rec. H.264 | ISO/IEC // 14496-10 video stream number xxxx // 3 F0 1111 0000 ECM_stream // F1 1111 0001 EMM_stream // 5 F2 1111 0010 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A or // ISO/IEC 13818-6_DSMCC_stream // 2 F3 1111 0011 ISO/IEC_13522_stream // 6 F4 1111 0100 ITU-T Rec. H.222.1 type A // 6 F5 1111 0101 ITU-T Rec. H.222.1 type B // 6 F6 1111 0110 ITU-T Rec. H.222.1 type C // 6 F7 1111 0111 ITU-T Rec. H.222.1 type D // 6 F8 1111 1000 ITU-T Rec. H.222.1 type E // 7 F9 1111 1001 ancillary_stream // FA 1111 1010 ISO/IEC14496-1_SL-packetized_stream // FB 1111 1011 ISO/IEC14496-1_FlexMux_stream // FC 1111 1100 descriptive data stream // FD 1111 1101 reserved data stream // FE 1111 1110 reserved data stream // 4 FF 1111 1111 program_stream_directory // // The notation x means that the values '0' or '1' are both permitted and // results in the same stream type. The stream number is given by the values // taken by the x's. // // NOTES // 1 PES packets of type program_stream_map have unique syntax specified // in 2.5.4.1. // 2 PES packets of type private_stream_1 and ISO/IEC_13552_stream follow // the same PES packet syntax as those for ITU-T Rec. H.262 | ISO/IEC // 13818-2 video and ISO/IEC 13818-3 audio streams. // 3 PES packets of type private_stream_2, ECM_stream and EMM_stream // are similar to private_stream_1 except no syntax is specified after // PES_packet_length field. // 4 PES packets of type program_stream_directory have a unique syntax // specified in 2.5.5. // 5 PES packets of type DSM-CC_stream have a unique syntax specified // in ISO/IEC 13818- 6. // 6 This stream_id is associated with stream_type 0x09 in Table 2-29. // 7 This stream_id is only used in PES packets, which carry data from // a Program Stream or an ISO/IEC 11172-1 System Stream, in a Transport // Stream (refer to 2.4.3.7). #define PADDING_STREAM_ID 0xBE #define PRIVATE1_AUDIO_STREAM_ID 0xBD #define PRIVATE2_AUDIO_STREAM_ID 0xBF #define DEFAULT_VIDEO_STREAM_ID 0xE0 // i.e., stream 0 #define DEFAULT_AUDIO_STREAM_ID 0xC0 // i.e., stream 0 #define IS_AUDIO_STREAM_ID(id) ((id)==0xBD || ((id) >= 0xC0 && (id) <= 0xDF)) #define IS_VIDEO_STREAM_ID(id) ((id) >= 0xE0 && (id) <= 0xEF) // ------------------------------------------------------------ // Timing info (used in reporting on packets). Initialise to all zeroes... struct timing { uint64_t first_pcr; uint64_t last_pcr; int first_pcr_packet; int last_pcr_packet; int had_first_pcr; // FALSE until we've started }; typedef struct timing *timing_p; #endif // _h222_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/h222_fns.h000066400000000000000000000025711261471605300163710ustar00rootroot00000000000000/* * Prototypes for functions relating to H.222 data, whether TS or PS * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _h222_fns #define _h222_fns // We don't include our corresponding header file, since there's // nothing we depend on from it. However, not that *it* includes // us, mainly for historical reasons. extern const char *h222_stream_type_str(unsigned s); #endif // _h222_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/h262.c000066400000000000000000001040601261471605300155160ustar00rootroot00000000000000/* * Datastructures and prototypes for reading H.262 (MPEG-2) elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "printing_fns.h" #include "h262_fns.h" #include "es_fns.h" #include "ts_fns.h" #include "reverse_fns.h" #include "misc_fns.h" #define DEBUG_GET_NEXT_PICTURE 0 #define DEBUG_AFD 0 /* * Print out information derived from the start code * * Note that if a "SYSTEM START" code is reported, then the data is * likely to be PES or Transport Stream data, not Elementary Stream. * * Similarly, if a "TRANSPORT STREAM sync byte" is reported, then * the stream is probably Transport Stream. * * If the stream is *not* Elementary Stream data, then it is possible * that some of the apparent start code prefixes are actually false * detections. */ extern void print_h262_start_code_str(byte start_code) { byte number; char *str = NULL; switch (start_code) { // H.262 start codes case 0x00: str = "Picture"; break; case 0xB0: str = "Reserved"; break; case 0xB1: str = "Reserved"; break; case 0xB2: str = "User data"; break; case 0xB3: str = "SEQUENCE HEADER"; break; case 0xB4: str = "Sequence error"; break; case 0xB5: str = "Extension start"; break; case 0xB6: str = "Reserved"; break; case 0xB7: str = "SEQUENCE END"; break; case 0xB8: str = "Group start"; break; // System start codes - 13818-1 p32 Table 2-18 stream_id // If these occur, then we're seeing PES headers // - maybe we're looking at transport stream data? case 0xBC: str = "SYSTEM START: Program stream map"; break; case 0xBD: str = "SYSTEM START: Private stream 1"; break; case 0xBE: str = "SYSTEM START: Padding stream"; break; case 0xBF: str = "SYSTEM START: Private stream 2"; break; case 0xF0: str = "SYSTEM START: ECM stream"; break; case 0xF1: str = "SYSTEM START: EMM stream"; break; case 0xF2: str = "SYSTEM START: DSMCC stream"; break; case 0xF3: str = "SYSTEM START: 13522 stream"; break; case 0xF4: str = "SYSTEM START: H.222 A stream"; break; case 0xF5: str = "SYSTEM START: H.222 B stream"; break; case 0xF6: str = "SYSTEM START: H.222 C stream"; break; case 0xF7: str = "SYSTEM START: H.222 D stream"; break; case 0xF8: str = "SYSTEM START: H.222 E stream"; break; case 0xF9: str = "SYSTEM START: Ancillary stream"; break; case 0xFF: str = "SYSTEM START: Program stream directory"; break; default: str = NULL; break; } if (str != NULL) print_msg(str); else if (start_code == 0x47) print_msg("TRANSPORT STREAM sync byte"); else if (start_code >= 0x01 && start_code <= 0xAF) fprint_msg("Slice, vertical posn %d",start_code); else if (start_code >= 0xC0 && start_code <=0xDF) { number = start_code & 0x1F; fprint_msg("SYSTEM START: Audio stream %02x",number); } else if (start_code >= 0xE0 && start_code <= 0xEF) { number = start_code & 0x0F; fprint_msg("SYSTEM START: Video stream %x",number); } else if (start_code >= 0xFC && start_code <= 0xFE) print_msg("SYSTEM START: Reserved data stream"); else print_msg("SYSTEM START: Unrecognised stream id"); } /* * Build a new MPEG2 item datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_h262_item(h262_item_p *item) { int err; h262_item_p new = malloc(SIZEOF_H262_ITEM); if (new == NULL) { print_err("### Unable to allocate MPEG2 item datastructure\n"); return 1; } err = setup_ES_unit(&(new->unit)); if (err) { print_err("### Unable to allocate MPEG2 item data buffer\n"); free(new); return 1; } *item = new; return 0; } /* * Tidy up and free an MPEG2 item datastructure after we've finished with it. * * Empties the MPEG2 item datastructure, frees it, and sets `item` to NULL. * * If `item` is already NULL, does nothing. */ extern void free_h262_item(h262_item_p *item) { if (*item == NULL) return; clear_ES_unit(&(*item)->unit); free(*item); *item = NULL; } /* * Print out useful information about this MPEG2 item */ extern void report_h262_item(h262_item_p item) { fprint_msg(OFFSET_T_FORMAT_08 "/%04d: MPEG2 item %02x (", item->unit.start_posn.infile, item->unit.start_posn.inpacket,item->unit.start_code); print_h262_start_code_str(item->unit.start_code); print_msg(")"); if (item->unit.start_code == 0) fprint_msg(" %d (%s)",item->picture_coding_type, H262_PICTURE_CODING_STR(item->picture_coding_type)); fprint_msg(" size %d",item->unit.data_len); print_msg("\n"); } // ------------------------------------------------------------ // MPEG2 item *data* stuff // ------------------------------------------------------------ /* * Find and read in the next MPEG2 item. * * Be careful if using this in conjunction with reading H.262 pictures * via an `h262_context_p`, as it does not maintain the "last item read" * information therein. * * - `es` is the elementary stream we're reading from. * - `item` is the datastructure containing the MPEG2 item found, or NULL * if there was none. * * Returns 0 if it succeeds, EOF if the end-of-file is read (i.e., there * is no next MPEG2 item), otherwise 1 if some error occurs. */ extern int find_next_h262_item(ES_p es, h262_item_p *item) { int err; err = build_h262_item(item); if (err) return 1; err = find_next_ES_unit(es,&(*item)->unit); if (err) // 1 or EOF { free_h262_item(item); return err; } // If this is a picture, we can do a little more if ((*item)->unit.start_code == 0) { (*item)->picture_coding_type = ((*item)->unit.data[5] & 0x38) >> 3; } return 0; } /* * Build a new H.262 picture reading context. * * This acts as a "jacket" around the ES context, and is used when reading * H.262 pictures with get_next_h262_picture(). It "remembers" the last * item read, which is the first item that was not part of the picture. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_h262_context(ES_p es, h262_context_p *context) { h262_context_p new = malloc(SIZEOF_H262_CONTEXT); if (new == NULL) { print_err("### Unable to allocate H.262 context datastructure\n"); return 1; } new->es = es; new->picture_index = 0; new->last_item = NULL; new->reverse_data = NULL; new->count_since_seq_hdr = 0; new->last_aspect_ratio_info = H262_UNSET_ASPECT_RATIO_INFO; new->last_afd = UNSET_AFD_BYTE; new->add_fake_afd = FALSE; *context = new; return 0; } /* * Free an H.262 picture reading context. * * Clears the datastructure, frees it, and returns `context` as NULL. * * Does not free any `reverse_data` datastructure. * * Does nothing if `context` is already NULL. */ extern void free_h262_context(h262_context_p *context) { h262_context_p cc = *context; if (cc == NULL) return; if (cc->last_item != NULL) free_h262_item(&cc->last_item); cc->reverse_data = NULL; free(*context); *context = NULL; return; } /* * Rewind a file being read as H.262 pictures * * This is a wrapper for `seek_ES` that also knows to unset things appropriate * to the H.262 picture reading context. * * If a reverse context is attached to this context, it also will * be "rewound" appropriately. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int rewind_h262_context(h262_context_p context) { ES_offset start_of_file = {0,0}; // First, forget where we are if (context->last_item) free_h262_item(&context->last_item); context->picture_index = 0; // no pictures read from this file yet // Next, take care of rewinding if (context->reverse_data) { context->reverse_data->last_posn_added = -1; // next entry to be 0 context->count_since_seq_hdr = 0; // what else can we do? } // And then, do the relocation itself return seek_ES(context->es,start_of_file); } // ------------------------------------------------------------ // MPEG2 "pictures" // ------------------------------------------------------------ /* * Add (the information from) an H.262 item to the given picture. * * Note that since this takes a copy of the ES unit data from within the item, * it is safe to free the original item. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int append_to_h262_picture(h262_picture_p picture, h262_item_p item) { ES_unit_p unit = &(item->unit); if (is_h262_extension_start_item(item)) { byte *data = unit->data; int extension_start_code_id = (data[4] & 0xF0) >> 4; if (extension_start_code_id == 1) // sequence extension { picture->progressive_sequence = data[5] & 0x08; } else if (extension_start_code_id == 8) // picture coding extension { picture->picture_structure = data[6] & 0x03; } } return append_to_ES_unit_list(picture->list,unit); } /* * Build a new H.262 "picture", starting with the given item (which is * copied, so may be freed after this call). * * Returns 0 if it succeeds, 1 if some error occurs. */ static int build_h262_picture(h262_context_p context, h262_picture_p *picture, h262_item_p item) { int err; ES_unit_p unit = &(item->unit); byte *data = unit->data; h262_picture_p new = malloc(SIZEOF_H262_PICTURE); if (new == NULL) { print_err("### Unable to allocate H.262 picture datastructure\n"); return 1; } err = build_ES_unit_list(&(new->list)); if (err) { print_err("### Unable to allocate internal list for H.262 picture\n"); free(new); return 1; } // Deduce what we can from the first item of the "picture" if (is_h262_picture_item(item)) { new->picture_coding_type = item->picture_coding_type; new->is_picture = TRUE; new->is_sequence_header = FALSE; new->temporal_reference = (data[4] << 2) | ((data[5] & 0xC0) >> 6); // Assume that our picture is a frame, until we're told otherwise // (MPEG-1 data will never tell us otherwise) new->picture_structure = 3; new->was_two_fields = FALSE; // Assume the last AFD and aspect ratio info until told otherwise new->afd = context->last_afd; new->aspect_ratio_info = context->last_aspect_ratio_info; new->is_real_afd = FALSE; } else if (is_h262_seq_header_item(item)) { new->is_picture = FALSE; new->is_sequence_header = TRUE; new->picture_coding_type = 0; // Forbidden value, just in case new->aspect_ratio_info = (data[7] & 0xF0) >> 4; // Assume that we are only allowed progressive frames, until we're told // otherwise (MPEG-1 data will never tell us otherwise) new->progressive_sequence = 1; } else if (is_h262_seq_end_item(item)) { new->is_picture = FALSE; new->is_sequence_header = FALSE; new->picture_coding_type = 0; // Forbidden value, just in case } else { fprint_err("!!! Building H.262 picture that starts with a %s (%02x)\n", H262_START_CODE_STR(item->unit.start_code),item->unit.start_code); new->is_picture = FALSE; new->is_sequence_header = FALSE; new->picture_coding_type = 0; // Forbidden value, just in case } err = append_to_h262_picture(new,item); if (err) { fprint_err("### Error appending first item to H.262 %s\n", H262_START_CODE_STR(item->unit.start_code)); free_h262_picture(&new); return 1; } *picture = new; return 0; } /* * Build a "pretend" H262 item containing an AFD user data field, and * append it to the given picture. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int append_fake_afd(h262_picture_p picture, byte afd) { int err; static h262_item_p item = NULL; if (item == NULL) { err = build_h262_item(&item); if (err) { print_err("### Error building 'fake' AFD for H.262 picture\n"); return 1; } item->unit.data[0] = 0x00; item->unit.data[1] = 0x00; item->unit.data[2] = 0x01; item->unit.data[3] = 0xb2; item->unit.data[4] = 0x44; item->unit.data[5] = 0x54; item->unit.data[6] = 0x47; item->unit.data[7] = 0x31; item->unit.data[8] = 0x41; item->unit.data[9] = afd; item->unit.data_len = 10; item->unit.start_code = 0xb2; } else item->unit.data[9] = afd; // Remember, this *copies* the item, so we can use it again later on err = append_to_h262_picture(picture,item); if (err) { print_err("### Error appending 'fake' AFD to H.262 picture\n"); return 1; } picture->afd = afd; picture->is_real_afd = FALSE; return 0; } /* * Merge two fields into one (frame) picture. * * - `picture1` is the first field. * - `picture2` is the second field, which will be merged into the first * (thus `picture2` may be freed after this function succeeds). * * Returns 0 if it succeeds, 1 if some error occurs. */ static int merge_fields(h262_picture_p picture1, h262_picture_p picture2) { int ii; for (ii = 0; ii < picture2->list->length; ii++) { int err = append_to_ES_unit_list(picture1->list, &picture2->list->array[ii]); if (err) { print_err("### Error merging two H.262 field pictures\n"); return 1; } } picture1->was_two_fields = TRUE; return 0; } /* * Free an H.262 "picture". * * Clears the datastructure, frees it, and returns `picture` as NULL. * * Does nothing if `picture` is already NULL. */ extern void free_h262_picture(h262_picture_p *picture) { h262_picture_p pic = *picture; if (pic == NULL) return; if (pic->list != NULL) free_ES_unit_list(&pic->list); free(*picture); *picture = NULL; return; } /* * Compare two H.262 pictures. The comparison does not include the start * position of the picture, but just the actual data - i.e., two pictures * read from different locations in the input stream may be considered the * same if their data content is identical. * * Returns TRUE if the lists contain identical content, FALSE otherwise. */ extern int same_h262_picture(h262_picture_p picture1, h262_picture_p picture2) { if (picture1 == picture2) return TRUE; else if (picture1 == NULL || picture2 == NULL) return FALSE; else return same_ES_unit_list(picture1->list,picture2->list); } /* * Remember a picture for future reversing, if it's an I picture or a * sequence header * * Returns 0 if it succeeds, 1 if some error occurs. */ static int maybe_remember_this_picture(h262_context_p h262, int verbose, h262_picture_p this_picture) { int err; ES_offset start_posn = {0,0}; uint32_t num_bytes = 0; if (this_picture->is_picture) { if (this_picture->picture_coding_type == 1) { // It's an I picture - we want to remember it in our reverse list (h262->count_since_seq_hdr) ++; err = get_ES_unit_list_bounds(this_picture->list,&start_posn,&num_bytes); if (err) { print_err("### Error working out position/size of H.262 picture\n"); return 1; } err = remember_reverse_h262_data(h262->reverse_data,h262->picture_index, start_posn,num_bytes, h262->count_since_seq_hdr, this_picture->afd); if (err) { print_err("### Error remembering reversing data for H.262 item\n"); return 1; } if (verbose) fprint_msg("REMEMBER I picture %5d at " OFFSET_T_FORMAT_08 "/%04d for %5d\n",h262->picture_index, start_posn.infile,start_posn.inpacket,num_bytes); } } else if (this_picture->is_sequence_header) { // It's a sequence header - remember it for the next picture h262->count_since_seq_hdr = 0; err = get_ES_unit_list_bounds(this_picture->list,&start_posn,&num_bytes); if (err) { print_err("### Error working out position/size of H.262" " sequence header for reversing data\n"); return 1; } err = remember_reverse_h262_data(h262->reverse_data,0, start_posn,num_bytes,0,0); if (err) { print_err("### Error remembering reversing data for H.262 item\n"); return 1; } if (verbose) fprint_msg("REMEMBER Sequence header at " OFFSET_T_FORMAT_08 "/%04d for %5d\n", start_posn.infile,start_posn.inpacket,num_bytes); } return 0; } /* * Given an MPEG-2 user data item containing an AFD (as indicated by the * ``is_h262_AFD_user_data_item`` macro), extract the actual AFD. * * NB: the whole byte containing the AFD is returned, including the top * '1111' bits. * * Returns 0 if all goes well, 1 if the AFD user data item is malformed * (in which case a message will have been written out to ``stderr``, but * the "apparent" AFD value will still be returned). */ static int extract_AFD(h262_item_p item, byte *afd) { if (item->unit.data[8] == 0x41) { // AFD flag set if (item->unit.data_len < 10) { fprint_err("!!! AFD too short (only %d bytes - AFD missing)\n", item->unit.data_len); *afd = UNSET_AFD_BYTE; return 1; } *afd = item->unit.data[9]; if ((item->unit.data[9] & 0xF0) != 0xF0) { fprint_err("### Bad AFD %02x (reserved bits not 1111)\n", item->unit.data[9]); return 1; } } else if (item->unit.data[8] == 0x01) { *afd = UNSET_AFD_BYTE; // no explicit AFD - use the default } else { fprint_err("### AFD datastructure malformed: flag byte is %02x" " instead of 0x41 or 0x01\n",item->unit.data[8]); if (item->unit.data_len == 9) *afd = UNSET_AFD_BYTE; else *afd = item->unit.data[9]; return 1; } return 0; } #if DEBUG_GET_NEXT_PICTURE /* * Print a representation of an item for debugging */ static void _show_item(h262_item_p item) { print_msg("__ "); if (item == NULL) { print_msg("\n"); return; } if (is_h262_picture_item(item)) fprint_msg("%s picture",H262_PICTURE_CODING_STR(item->picture_coding_type)); else if (is_h262_slice_item(item)) fprint_msg("slice %2x",item->unit.start_code); else fprint_msg("%s",H262_START_CODE_STR(item->unit.start_code)); fprint_msg(" at " OFFSET_T_FORMAT "/%d for %d\n", item->unit.start_posn.infile,item->unit.start_posn.inpacket, item->unit.data_len); } #endif /* * Retrieve the the next H.262 "picture". * * The H.262 "picture" returned can be one of: * * 1. A field or frame, including its slices. * 2. A sequence header, including its sequence extension, if any. * 3. A sequence end. * * - `context` is the H.262 picture reading context. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `picture` is the H.262 "picture", containing a field or frame picture, * a sequence header or a sequence end * * Returns 0 if it succeeds, EOF if we reach the end of file, or 1 if some * error occurs. */ extern int get_next_h262_single_picture(h262_context_p context, int verbose, h262_picture_p *picture) { int err = 0; int in_sequence_header = FALSE; int in_sequence_end = FALSE; int in_picture = FALSE; int last_was_slice = FALSE; int had_afd = FALSE; h262_item_p item = context->last_item; #if DEBUG_GET_NEXT_PICTURE int num_slices = 0; int had_slice = FALSE; int last_slice_start_code = 0; if (verbose && context->last_item) print_msg("__ reuse last item\n"); #endif context->last_item = NULL; // Find the first item of our next "picture" for (;;) { if (item == NULL) { err = find_next_h262_item(context->es,&item); if (err) return err; } if (is_h262_picture_item(item)) { in_picture = TRUE; break; } else if (is_h262_seq_header_item(item)) { in_sequence_header = TRUE; break; } else if (is_h262_seq_end_item(item)) { in_sequence_end = TRUE; break; } #if DEBUG_GET_NEXT_PICTURE else if (verbose) _show_item(item); #endif free_h262_item(&item); } #if DEBUG_GET_NEXT_PICTURE if (verbose) { print_msg("__ --------------------------------- \n"); _show_item(item); } #endif err = build_h262_picture(context,picture,item); if (err) return 1; free_h262_item(&item); if (in_sequence_end) { // A sequence end is a single item, so we're done #if DEBUG_GET_NEXT_PICTURE if (verbose) print_msg("__ --------------------------------- \n"); #endif return 0; } // Now find all the rest of the picture/sequence header for (;;) { err = find_next_h262_item(context->es,&item); if (err) { if (err != EOF) free_h262_picture(picture); return err; } if (in_picture) { // Have we just finished a picture? // We know we have if the last item was a slice, but this one isn't if (last_was_slice && !is_h262_slice_item(item)) break; last_was_slice = is_h262_slice_item(item); } else if (in_sequence_header) { // Have we just finished a sequence header and its friends? // We know we have if we've hit something that isn't an // extension start or user data start code (perhaps we could // get away with just keeping the (in MPEG-2) sequence_extension, // but it's safer (and simpler) to keep the lot if (!is_h262_extension_start_item(item) && !is_h262_user_data_item(item)) break; } if (in_picture) { if (is_h262_AFD_user_data_item(item)) { // We found a *real* AFD - remember it err = extract_AFD(item,&(*picture)->afd); if (err) fprint_err("!!! Assuming AFD %x at " OFFSET_T_FORMAT "/%d\n", (*picture)->afd, item->unit.start_posn.infile,item->unit.start_posn.inpacket); (*picture)->is_real_afd = TRUE; #if DEBUG_AFD if ((*picture)->afd != context->last_afd) { print_msg("* "); report_h262_picture(stdout,*picture,FALSE); } #endif context->last_afd = (*picture)->afd; had_afd = TRUE; } else if (context->add_fake_afd && !had_afd && is_h262_slice_item(item)) { // We've been asked to fake AFDs for pictures that don't have them, // and this is the first slice of a picture, so now (i.e., before // said first slice) is the time to add in that faked AFD err = append_fake_afd(*picture,context->last_afd); if (err) { free_h262_picture(picture); return 1; } had_afd = TRUE; // well, sort of #if DEBUG_GET_NEXT_PICTURE if (verbose) { print_msg("__ fake AFD "); print_bits(4,(*picture)->afd); fprint_msg(", i.e., %s",SHORT_AFD_STR((*picture)->afd)); print_msg("\n"); } #endif } } #if DEBUG_GET_NEXT_PICTURE if (verbose) { if (!had_slice) _show_item(item); if (is_h262_slice_item(item)) { num_slices ++; last_slice_start_code = item->unit.start_code; if (!had_slice) had_slice = TRUE; } } #endif // Don't forget to remember the actual item err = append_to_h262_picture(*picture,item); if (err) { print_err("### Error adding item to H.262 sequence header\n"); free_h262_picture(picture); return 1; } free_h262_item(&item); } if (in_picture) context->picture_index ++; else context->last_aspect_ratio_info = (*picture)->aspect_ratio_info; context->last_item = item; #if DEBUG_GET_NEXT_PICTURE if (verbose) { if (in_picture) { if (num_slices > 1) { ES_unit_p unit = &(*picture)->list->array[(*picture)->list->length-1]; print_msg("__ ...\n"); fprint_msg("__ slice %2x",last_slice_start_code); fprint_msg(" at " OFFSET_T_FORMAT "/%d for %d\n", unit->start_posn.infile,unit->start_posn.inpacket, unit->data_len); } fprint_msg("__ (%2d slices)\n",num_slices); } print_msg("__ --------------------------------- \n"); if (in_picture || in_sequence_header) _show_item(item); } #endif return 0; } /* * Try for the next field of a pair, and return a frame formed thereof * * - `context` is the H.262 picture reading context. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - if `first_time` is true, then we will try to match a second field * with a third, if the second field has a different temporal reference * than the first. If it is false, we will not (thus stopping us from * trying forever...) * - `picture` starts out at the first field of our (hoped for) pair, and * will end up as the merged result of our two fields. If the input stream * is awry (or we are misaligned with respect to it), this might instead be * replaced by a "proper" frame, or even a sequence header. * * Returns 0 if it succeeds, EOF if we reach the end of file or have * read the sequence_end_code, or 1 if some error occurs. */ static int get_next_field_of_pair(h262_context_p context, int verbose, int quiet, int first_time, h262_picture_p *picture) { int err; h262_picture_p second; if (verbose) fprint_msg("@@ Looking for second field (%s time)\n", (first_time?"first":"second")); // We assume (hope) the next picture will be our second half err = get_next_h262_single_picture(context,verbose,&second); if (err) { if (err != EOF) print_err("### Trying to read second field\n"); return err; } if (!is_h262_field_picture(second)) { // But it was either a frame or a sequence header - oh dear if (!quiet) fprint_err("!!! Field followed by a %s - ignoring the field\n", (second->is_picture?"frame":"sequence header")); free_h262_picture(picture); *picture = second; // and pretend to success } else if ((*picture)->temporal_reference == second->temporal_reference) { // They appear to be matching fields - make a frame from them if (verbose) print_msg("@@ Merging two fields\n"); err = merge_fields(*picture,second); if (err) { free_h262_picture(&second); return 1; } free_h262_picture(&second); } else if (first_time) { if (!quiet) fprint_err("!!! Field with temporal ref %d (%x) followed by" " field with temporal ref %d (%x) - ignoring first field\n", (*picture)->temporal_reference,(*picture)->temporal_reference, second->temporal_reference,second->temporal_reference); // Try again free_h262_picture(picture); *picture = second; err = get_next_field_of_pair(context,verbose,quiet,FALSE,picture); if (err) return 1; } else { print_err("### Adjacent fields do not share temporal references" " - unable to match fields up\n"); return 1; } return 0; } /* * Retrieve the the next H.262 "picture". * * The H.262 "picture" returned can be one of: * * 1. A frame, including its slices. This may be the concatenation of two * adjacent field pictures. * 2. A sequence header, including its sequence extension, if any. * 3. A sequence end. * * Specifically, the next H.262 "picture" is retrieved from the input stream. * * If that "picture" represents a sequence header or a frame, it is returned. * * If it represents a field, then the *following* "picture" is retrieved, and * if that is the second field of its frame, it is merged into the first, * and the resultant frame is returned. * * If a field with temporal reference A is followed by a field with temporal * reference B, it is assumed that synchronisation has been lost. In this * case, the first field (frame A) will be discarded, and an attempt made to * read the second field of frame B. * * Similarly, if a frame or sequence header is found instead of the second * field, the first field will be discarded and the frame returned. * * Note that if the context is associated with a reverse context, * then appropriate frames/sequence headers will automatically be * remembered therein. * * Also note that it is assumed that the AFD for adjacent fields will be * the same. * * - `context` is the H.262 picture reading context. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `picture` is the H.262 "picture", containing a frame picture, * a sequence header or a sequence end * * Returns 0 if it succeeds, EOF if we reach the end of file, or 1 if some * error occurs. */ extern int get_next_h262_frame(h262_context_p context, int verbose, int quiet, h262_picture_p *picture) { int err; err = get_next_h262_single_picture(context,verbose,picture); if (err) return err; if (is_h262_field_picture(*picture)) { // We assume (hope) the next picture will be our second half // - let's try to get it, and merge it into our current picture err = get_next_field_of_pair(context,verbose,quiet,TRUE,picture); if (err) { free_h262_picture(picture); return 1; } } if (context->reverse_data) { err = maybe_remember_this_picture(context,verbose,*picture); if (err) { free_h262_picture(picture); return 1; } } return 0; } /* * Write out an H.262 picture as TS * * - `tswriter` is TS the output stream * - `picture` is the picture to write out * - `pid` is the PID to use for the TS packets * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_h262_picture_as_TS(TS_writer_p tswriter, h262_picture_p picture, uint32_t pid) { int ii; ES_unit_list_p list; if (picture == NULL || picture->list == NULL) return 0; list = picture->list; for (ii = 0; ii < list->length; ii++) { int err; ES_unit_p unit = &(list->array[ii]); err = write_ES_as_TS_PES_packet(tswriter,unit->data,unit->data_len,pid, DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing out picture list to TS\n"); return err; } } return 0; } /* * Write out a picture (as stored in an ES unit list) as ES * * - `output` is the ES output file * - `picture` is the picture to write out * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_h262_picture_as_ES(FILE *output, h262_picture_p picture) { int ii; ES_unit_list_p list; if (picture == NULL || picture->list == NULL) return 0; list = picture->list; for (ii = 0; ii < list->length; ii++) { int err; ES_unit_p unit = &(list->array[ii]); err = write_ES_unit(output,unit); if (err) { print_err("### Error writing out picture list to ES\n"); return err; } } return 0; } /* * Report on an H.262 picture's contents. * * - `stream` is where to write the information * - `picture` is the picture to report on * - if `report_data`, then the component ES units will be printed out as well */ extern void report_h262_picture(h262_picture_p picture, int report_data) { if (picture->is_picture) { fprint_msg("%s %s #%02d", H262_PICTURE_CODING_STR(picture->picture_coding_type), H262_PICTURE_STRUCTURE_STR(picture->picture_structure), picture->temporal_reference); if (picture->was_two_fields) print_msg(" (merged)"); fprint_msg(" %s",H262_ASPECT_RATIO_INFO_STR(picture->aspect_ratio_info)); if (picture->is_real_afd) print_msg(" AFD "); else print_msg(" afd "); print_bits(4,picture->afd); fprint_msg(", i.e., %s",SHORT_AFD_STR(picture->afd)); print_msg("\n"); } else if (picture->is_sequence_header) { print_msg("Sequence header: "); switch (picture->progressive_sequence) { case 0: print_msg("frames and fields"); break; case 1: print_msg("progressive frames only"); break; default: fprint_msg("progressive_sequence=%d", picture->progressive_sequence); break; } fprint_msg(", aspect ratio %s", H262_ASPECT_RATIO_INFO_STR(picture->aspect_ratio_info)); print_msg("\n"); } else { print_msg("Sequence end\n"); } if (report_data) report_ES_unit_list("ES units",picture->list); } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/h262_defns.h000066400000000000000000000256421261471605300167120ustar00rootroot00000000000000/* * Datastructures for reading H.262 (MPEG-2) elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _h262_defns #define _h262_defns #include #include "compat.h" #include "es_defns.h" #include "ts_defns.h" // Since reverse_data refers to h262 and acces_unit datastructures, and // *they* refer to reverse_data, we need to break the circular referencing // at some point typedef struct h262_context *h262_context_p; struct reverse_data; // ------------------------------------------------------------ // An MPEG "item", the set of bytes that starts with a start code prefix. struct _h262_item { struct ES_unit unit; // The actual data // MPEG2 specific data byte picture_coding_type; // only defined if unit.start_code == 0 }; typedef struct _h262_item *h262_item_p; #define SIZEOF_H262_ITEM sizeof(struct _h262_item) // ------------------------------------------------------------ // An H.262 "picture". This might be a picture or a sequence header (and // its associated items). struct _h262_picture { // The main thing we need is a list of the items that make up this picture ES_unit_list_p list; // An H.262 "picture" might be a "proper" picture, a sequence header, // or (just) a sequence end item. It's useful to be able to identify // the two more common cases easily int is_picture; int is_sequence_header; // Data defined for a picture. When a picture is composed of the data // from two fields, then these will be the values taken from the first // field "picture". byte picture_coding_type; // I, P or B byte picture_structure; // top/bottom field or frame uint16_t temporal_reference; // presentation order within a group byte afd; // its "Active Format Description" value // (NB: with 0xF0 bits set at top of byte) byte is_real_afd; // was it a *real* AFD? int was_two_fields; // TRUE if it's a frame merged from two fields // Data defined for a sequence header/extension // Note that H.262 requires that data given in one sequence extension // shall be the same as that in all the others in the stream. Thus, in // particular, we know that if fields are allowed by one sequence // extension, they will be allowed by all. byte progressive_sequence; // frames or frames and fields allowed? // Data defined for both // (in a frame, this is the value from the previous section header) byte aspect_ratio_info; // its aspect ratio code }; typedef struct _h262_picture *h262_picture_p; #define SIZEOF_H262_PICTURE sizeof(struct _h262_picture) // ------------------------------------------------------------ // Produce a nice string for the start code. `b` must be a byte. #define H262_START_CODE_STR(b) \ ((b)==0x00?"picture": \ (b)>=0x01 && b<=0xAF?"slice": \ (b)==0xB0?"reserved": \ (b)==0xB1?"reserved": \ (b)==0xB2?"user data": \ (b)==0xB3?"sequence header": \ (b)==0xB4?"sequence error": \ (b)==0xB5?"extension start": \ (b)==0xB6?"reserved": \ (b)==0xB7?"sequence end": \ (b)==0xB8?"group start": \ (b)>=0xB9?"system start":"???") #define is_h262_picture_item(item) ((item)->unit.start_code==0x00) #define is_h262_slice_item(item) ((item)->unit.start_code>=0x01 && \ (item)->unit.start_code<=0xAF) #define is_h262_user_data_item(item) ((item)->unit.start_code==0xB2) #define is_h262_seq_header_item(item) ((item)->unit.start_code==0xB3) #define is_h262_seq_end_item(item) ((item)->unit.start_code==0xB7) #define is_h262_group_start_item(item) ((item)->unit.start_code==0xB8) #define is_h262_extension_start_item(item) ((item)->unit.start_code==0xB5) #define H262_PICTURE_CODING_STR(s) \ ((s)==0?"Forbidden": \ (s)==1?"I": \ (s)==2?"P": \ (s)==3?"B": \ (s)==4?"D":"Reserved") // The following two macros can be used on H.262 items *or* pictures #define is_I_picture(picture) ((picture)->picture_coding_type==1) #define is_P_picture(picture) ((picture)->picture_coding_type==2) #define H262_PICTURE_STRUCTURE_STR(s) \ ((s)==0?"Reserved": \ (s)==1?"Top Field": \ (s)==2?"Bottom Field": \ (s)==3?"Frame":"???") #define is_h262_field_picture(picture) ((picture)->is_picture && \ ((picture)->picture_structure == 1 ||\ (picture)->picture_structure == 2)) #define is_h262_AFD_user_data_item(item) \ ((item)->unit.start_code == 0xB2 && \ (item)->unit.data_len > 8 && \ (item)->unit.data[4] == 0x44 && \ (item)->unit.data[5] == 0x54 && \ (item)->unit.data[6] == 0x47 && \ (item)->unit.data[7] == 0x31) #define UNSET_AFD_BYTE 0xF8 // i.e., '1111 1000' // String values taken from ATSC Digital Television Standard, Rev C, // (A/53C) 12 May 2004, which will hopefully be correct for DVB as well... #define AFD_STR(afd) \ ((afd)==0xF2?"ATSC: box 16:9 (top)": \ (afd)==0xF3?"ATSC: box 14:9 (top)": \ (afd)==0xF4?"ATSC: box > 16:9 (center)": \ (afd)==0xF8?"Active format as coded frame": \ (afd)==0xF9?"4:3 (centre)": \ (afd)==0xFA?"16:9 (centre)": \ (afd)==0xFB?"14:9 (centre)": \ (afd)==0xFC?"reserved": \ (afd)==0xFD?"4:3 (with shoot & protect 14:9 centre)": \ (afd)==0xFE?"16:9 (with shoot & protect 14:9 centre)": \ (afd)==0xFF?"16:9 (with shoot & protect 4:3 centre)":"reserved") #define SHORT_AFD_STR(afd) \ ((afd)==0xF2?"ATSC: box 16:9 (top)": \ (afd)==0xF3?"ATSC: box 14:9 (top)": \ (afd)==0xF4?"ATSC: box > 16:9 (center)": \ (afd)==0xF8?"As coded frame": \ (afd)==0xF9?"4:3 (centre)": \ (afd)==0xFA?"16:9 (centre)": \ (afd)==0xFB?"14:9 (centre)": \ (afd)==0xFC?"reserved": \ (afd)==0xFD?"4:3 (14:9)": \ (afd)==0xFE?"16:9 (14:9)": \ (afd)==0xFF?"16:9 (4:3)":"reserved") // The standard does not define value FF, so I'm choosing it as an "unset" // value, so that I get sensible diagnostics when a picture is reported // on before a section header has been read #define H262_UNSET_ASPECT_RATIO_INFO 0xFF #define H262_ASPECT_RATIO_INFO_STR(rat) ((rat)==0xFF?"Unset": \ (rat)==0?"Forbidden aspect ratio": \ (rat)==1?"Square": \ (rat)==2?"4:3": \ (rat)==3?"16:9": \ "Reserved aspect ratio") // ------------------------------------------------------------ // Context for looping over the H.262 items and pictures in an elementary // stream struct h262_context { ES_p es; // We count all of the pictures as we read them (this is useful // when we are building up reverse_data arrays). If functions // move around in the data stream, we assume that they will // (re)set this to a sensible value. // The index of the first picture read is 1, and this value is // incremented by each call of `get_next_h262_picture` (note that // for this purpose, sequence headers are *not* considered pictures) uint32_t picture_index; // The index of the last picture read // We detect the end of an H.262 picture (or sequence header) by // reading the first item that cannot be part of it. We then need // to remember that item for *next* time we try to read a picture. h262_item_p last_item; // What was the aspect ratio code from the last sequence header? byte last_aspect_ratio_info; // When we are reading MPEG-2, we may encounter AFD (Active Format // Definiton) user data. This sets the aspect ratio of the following // picture(s), possibly overriding information present in a preceding // sequence header. It should only be present for I pictures, really, // but we remember it for all pictures. // Once an AFD value has been set, it stands until another value is read. // This all becomes important when reversing, which see. // Anyway, in order to set the AFD on pictures which don't have it // explicitly, we obviously need to remember the last value found. // For simplicity of use, we remember the whole byte it came in, // including the 0xF0 reserved bits at the top of the byte. // Note that '1000' (8) is the "unset" value. byte last_afd; // When we are reading an H.262 picture back in, for reversing purposes, or // reading frames for filtering, it is useful to *insist* that an AFD be // found for the picture. Thus, if `add_fake_afd` is TRUE, a dummy AFD, // containing the value in `last_afd`, will be inserted into the picture's // ES unit `list` (of course, if the picture contains a *real* AFD, this is // not necessary). Beware that the ES unit inserted won't stand up to close // observation, as its start position (for instance) will clearly be // wrong... // (this is manipulated by the reversing and filtering code - it is not // intended for use for any other purpose) int add_fake_afd; // If we are collecting reversing information, then we keep a reference // to the reverse data here struct reverse_data * reverse_data; // In the same context, we need to remember how long it is since the // last sequence header byte count_since_seq_hdr; }; #define SIZEOF_H262_CONTEXT sizeof(struct h262_context) #endif // _h262_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/h262_fns.h000066400000000000000000000204501261471605300163710ustar00rootroot00000000000000/* * Prototypes for reading H.262 (MPEG-2) elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _h262_fns #define _h262_fns #include "h262_defns.h" /* * Print out information derived from the start code * * Note that if a "SYSTEM START" code is reported, then the data is * likely to be PES or Transport Stream data, not Elementary Stream. * * Similarly, if a "TRANSPORT STREAM sync byte" is reported, then * the stream is probably Transport Stream. * * If the stream is *not* Elementary Stream data, then it is possible * that some of the apparent start code prefixes are actually false * detections. */ extern void print_h262_start_code_str(byte start_code); /* * Build a new MPEG2 item datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_h262_item(h262_item_p *item); /* * Tidy up and free an MPEG2 item datastructure after we've finished with it. * * Empties the MPEG2 item datastructure, frees it, and sets `item` to NULL. * * If `item` is already NULL, does nothing. */ extern void free_h262_item(h262_item_p *item); /* * Print out useful information about this MPEG2 item */ extern void report_h262_item(h262_item_p item); // ------------------------------------------------------------ // MPEG2 item *data* stuff // ------------------------------------------------------------ /* * Find and read in the next MPEG2 item. * * Be careful if using this in conjunction with reading H.262 pictures * via an `h262_context_p`, as it does not maintain the "last item read" * information therein. * * - `es` is the elementary stream we're reading from. * - `item` is the datastructure containing the MPEG2 item found, or NULL * if there was none. * * Returns 0 if it succeeds, EOF if the end-of-file is read (i.e., there * is no next MPEG2 item), otherwise 1 if some error occurs. */ extern int find_next_h262_item(ES_p es, h262_item_p *item); /* * Build a new H.262 picture reading context. * * This acts as a "jacket" around the ES context, and is used when reading * H.262 pictures with get_next_h262_picture(). It "remembers" the last * item read, which is the first item that was not part of the picture. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_h262_context(ES_p es, h262_context_p *context); /* * Free an H.262 picture reading context. * * Clears the datastructure, frees it, and returns `context` as NULL. * * Does not free any `reverse_data` datastructure. * * Does nothing if `context` is already NULL. */ extern void free_h262_context(h262_context_p *context); /* * Rewind a file being read as H.262 pictures * * This is a wrapper for `seek_ES` that also knows to unset things appropriate * to the H.262 picture reading context. * * If a reverse context is attached to this context, it also will * be "rewound" appropriately. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int rewind_h262_context(h262_context_p context); /* * Free an H.262 "picture". * * Clears the datastructure, frees it, and returns `picture` as NULL. * * Does nothing if `picture` is already NULL. */ extern void free_h262_picture(h262_picture_p *picture); /* * Compare two H.262 pictures. The comparison does not include the start * position of the picture, but just the actual data - i.e., two pictures * read from different locations in the input stream may be considered the * same if their data content is identical. * * Returns TRUE if the lists contain identical content, FALSE otherwise. */ extern int same_h262_picture(h262_picture_p picture1, h262_picture_p picture2); /* * Retrieve the the next H.262 "picture". * * The H.262 "picture" returned can be one of: * * 1. A field or frame, including its slices. * 2. A sequence header, including its sequence extension, if any. * 3. A sequence end. * * - `context` is the H.262 picture reading context. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `picture` is the H.262 "picture", containing a field or frame picture, * a sequence header or a sequence end * * Returns 0 if it succeeds, EOF if we reach the end of file, or 1 if some * error occurs. */ extern int get_next_h262_single_picture(h262_context_p context, int verbose, h262_picture_p *picture); /* * Retrieve the the next H.262 "picture". * * The H.262 "picture" returned can be one of: * * 1. A frame, including its slices. This may be the concatenation of two * adjacent field pictures. * 2. A sequence header, including its sequence extension, if any. * 3. A sequence end. * * Specifically, the next H.262 "picture" is retrieved from the input stream. * * If that "picture" represents a sequence header or a frame, it is returned. * * If it represents a field, then the *following* "picture" is retrieved, and * if that is the second field of its frame, it is merged into the first, * and the resultant frame is returned. * * If a field with temporal reference A is followed by a field with temporal * reference B, it is assumed that synchronisation has been lost. In this * case, the first field (frame A) will be discarded, and an attempt made to * read the second field of frame B. * * Similarly, if a frame or sequence header is found instead of the second * field, the first field will be discarded and the frame returned. * * Note that if the context is associated with a reverse context, * then appropriate frames/sequence headers will automatically be * remembered therein. * * - `context` is the H.262 picture reading context. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `picture` is the H.262 "picture", containing a frame picture, * a sequence header or a sequence end * * Returns 0 if it succeeds, EOF if we reach the end of file, or 1 if some * error occurs. */ extern int get_next_h262_frame(h262_context_p context, int verbose, int quiet, h262_picture_p *picture); /* * Write out an H.262 picture as TS * * - `tswriter` is TS the output stream * - `picture` is the picture to write out * - `pid` is the PID to use for the TS packets * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_h262_picture_as_TS(TS_writer_p tswriter, h262_picture_p picture, uint32_t pid); /* * Write out a picture (as stored in an ES unit list) as ES * * - `output` is the ES output file * - `picture` is the picture to write out * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int write_h262_picture_as_ES(FILE *output, h262_picture_p picture); /* * Report on an H.262 picture's contents. * * - `picture` is the picture to report on * - if `report_data`, then the component ES units will be printed out as well */ extern void report_h262_picture(h262_picture_p picture, int report_data); #endif // _h262_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ipv4.c000066400000000000000000000054571261471605300157310ustar00rootroot00000000000000/* ipv4.c */ /* * Routines for dissecting ipv4 * * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Richard Watts, Kynesim * * ***** END LICENSE BLOCK ***** */ #include #include #include "ipv4.h" #include "misc_fns.h" #include int ipv4_from_payload(const uint8_t *data, const uint32_t len, ipv4_header_t *out_hdr, uint32_t *out_st, uint32_t *out_len) { uint32_t cur_field; // Min length of an ipv4 header is 20 bytes (5 words) if (len < 20) { return IPV4_ERR_PKT_TOO_SHORT; } // Field 0 cur_field = uint_32_be(&data[0]); out_hdr->version = (cur_field >> 28)&0xf; out_hdr->hdr_length = (cur_field >> 24)& 0xf; out_hdr->serv_type = (cur_field >> 16) & 0xff; out_hdr->length = (cur_field) & 0xffff; // Field 1 cur_field = uint_32_be(&data[4]); out_hdr->ident = (cur_field >> 16) & 0xffff; out_hdr->flags = (cur_field >> 13) & 7; out_hdr->frag_offset = (cur_field & 0x1fff); // Field 2 cur_field = uint_32_be(&data[8]); out_hdr->ttl = (cur_field >> 24) & 0xff; out_hdr->proto = (cur_field >> 16) & 0xff; out_hdr->csum = (cur_field) & 0xffff; // Field 3 - src address. out_hdr->src_addr = uint_32_be(&data[12]); // Field 4 - dest address. out_hdr->dest_addr = uint_32_be(&data[16]); // Now the data .. (*out_st) = (out_hdr->hdr_length << 2); (*out_len) = len - (out_hdr->hdr_length << 2); return 0; } int ipv4_udp_from_payload(const uint8_t *data, const uint32_t len, ipv4_udp_header_t *out_hdr, uint32_t *out_st, uint32_t *out_len) { uint32_t cur_field; // UDP headers are 8 bytes long. if (len < 8) { return IPV4_ERR_PKT_TOO_SHORT; } cur_field = uint_32_be(&data[0]); out_hdr->source_port = (cur_field >> 16)&0xffff; out_hdr->dest_port = (cur_field) & 0xffff; cur_field = uint_32_be(&data[4]); out_hdr->length = (cur_field >> 16) & 0xffff; out_hdr->csum = (cur_field) &0xffff; (*out_st) = 8; (*out_len) = len - 8; return 0; } /* End file */ tstools-1.13~git20151030/ipv4.h000066400000000000000000000051051261471605300157240ustar00rootroot00000000000000/* ipv4.h */ /* * Routines for dissecting IPv4 and UDP packets. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Richard Watts, Kynesim * * ***** END LICENSE BLOCK ***** */ #ifndef _ipv4_h #define _ipv4_h #include "compat.h" /*! This is all held in host byte order (including the * IP addresses! You have been warned .. ) */ typedef struct ipv4_header_s { // IP version. Should really be 4 :-) uint8_t version; // Header length. uint8_t hdr_length; // Type of service. uint8_t serv_type; //! Total length uint16_t length; //! Ident uint16_t ident; //! Flags uint8_t flags; //! Frag offset. uint16_t frag_offset; //! TTL uint8_t ttl; //! Protocol (typically UDP or TCP) uint8_t proto; //! Header checksum (we don't check this!) uint16_t csum; //! Source address. uint32_t src_addr; //! Destination address. uint32_t dest_addr; //! We don't track options. } ipv4_header_t; // Is this header UDP? #define IPV4_HDR_IS_UDP(h) ((h)->proto == 17) #define IPV4_ERR_PKT_TOO_SHORT (-1) /*! * Unwrap ipv4. * * \param out_st OUT Index into data at which IPv4 payload starts. * \param out_len OUT Length of the IPv4 payload. * \return 0 on success, -1 on failure. */ int ipv4_from_payload(const uint8_t *data, const uint32_t len, ipv4_header_t *out_hdr, uint32_t *out_st, uint32_t *out_len); typedef struct ipv4_udp_header_s { //! Source port. uint16_t source_port; //! Dest port. uint16_t dest_port; //! Length (yes, yet another one!) uint16_t length; //! Checksum uint16_t csum; } ipv4_udp_header_t; /*! * Unwrap UDP. * */ int ipv4_udp_from_payload(const uint8_t *data, const uint32_t len, ipv4_udp_header_t *out_hdr, uint32_t *out_st, uint32_t *out_len); #endif /* End file */ tstools-1.13~git20151030/l2audio.c000066400000000000000000000240211261471605300163720ustar00rootroot00000000000000/* * Support for MPEG layer 2 audio streams. * * (actually, support for * * - MPEG-1 audio (described in ISO/IEC 11172-3), layers 1..3 * - MPEG-2 audio (described in ISO/IEC 13818-3), layer 2 * - unofficial MPEG-2.5 * * but MPEG-2 layer 2 is the main target) * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "misc_fns.h" #include "printing_fns.h" #include "l2audio_fns.h" #define DEBUG 0 // Bitrates by index, according to layer and protocol version // Note that v3 is actually the mutant V2.5 protocol static const int bitrate_v1l1[] = { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }; static const int bitrate_v1l2[] = { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }; static const int bitrate_v1l3[] = { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }; static const int bitrate_v2l1[] = { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 }; static const int bitrate_v2l2[] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }; static const int * const bitrate_table[3][3] = { { bitrate_v1l1, bitrate_v1l2, bitrate_v1l3 }, { bitrate_v2l1, bitrate_v2l2, bitrate_v2l2 }, { bitrate_v2l1, bitrate_v2l2, bitrate_v2l2 } }; // Sample rates table static const unsigned int sampling_table[3][3] = { { 44100, 48000, 32000 }, { 22050, 24000, 16000 }, { 11025, 12000, 8000 } }; // Decode frame header information #define AUD_FRAME_RATE_N_0 96000 #define AUD_FRAME_RATE_N_1 88200 #define AUD_FRAME_RATE_N_2 64000 #define AUD_FRAME_RATE_N_3 48000 #define AUD_FRAME_RATE_N_4 44100 #define AUD_FRAME_RATE_N_5 32000 #define AUD_FRAME_RATE_N_6 24000 #define AUD_FRAME_RATE_N_7 22050 #define AUD_FRAME_RATE_N_8 16000 #define AUD_FRAME_RATE_N_9 12000 #define AUD_FRAME_RATE_N_10 11025 #define AUD_FRAME_RATE_N_11 8000 #define AUD_FRAME_RATE_N_12 7350 #define AUD_FRAME_RATE_N_13 0 #define AUD_FRAME_RATE_N_14 0 #define AUD_FRAME_RATE_N_15 0 const unsigned int aud_frame_rate_n[16] = { AUD_FRAME_RATE_N_0, AUD_FRAME_RATE_N_1, AUD_FRAME_RATE_N_2, AUD_FRAME_RATE_N_3, AUD_FRAME_RATE_N_4, AUD_FRAME_RATE_N_5, AUD_FRAME_RATE_N_6, AUD_FRAME_RATE_N_7, AUD_FRAME_RATE_N_8, AUD_FRAME_RATE_N_9, AUD_FRAME_RATE_N_10, AUD_FRAME_RATE_N_11, AUD_FRAME_RATE_N_12, AUD_FRAME_RATE_N_13, AUD_FRAME_RATE_N_14, AUD_FRAME_RATE_N_15 }; /* * Look at a frame header and try to deduce the length of the frame. * * Returns the frame length deduced therefrom, or -1 if it finds something * wrong with the header data. */ static int peek_frame_header(const uint32_t header) { unsigned int version, layer, padding; // byte protected, private; // byte mode, modex, copyright, original, emphasis; unsigned int bitrate_enc, sampling_enc; unsigned int bitrate; // unsigned int sampling; byte rate; // unsigned int framesize unsigned int framelen; // Version: // 00 - MPEG Version 2.5 // 01 - reserved // 10 - MPEG Version 2 (ISO/IEC 13818-3) // 11 - MPEG Version 1 (ISO/IEC 11172-3) version = (header >> 19) & 0x03; if (version == 1) { print_err("### Illegal version (1) in MPEG layer 2 audio header\n"); return -1; } version = (version == 3) ? 1: (version == 2) ? 2: 3; // Layer: // 00 - reserved // 01 - Layer 3 // 10 - Layer 2 // 11 - Layer 1 layer = (header >> 17) & 0x03; if (layer == 0) { print_err("### Illegal layer (0) in MPEG layer 2 audio header\n"); return -1; } layer = 4 - layer; // protected (i.e. CRC present) field // protected = ! ((header >> 16) & 0x01); // bitrate field, whose meaning is dependent on version and layer bitrate_enc = (header >> 12) & 0x0f; if (bitrate_enc == 0x0f) { print_err("### Illegal bitrate_enc (0x0f) in MPEG layer 2 audio header\n"); return -1; } bitrate = (bitrate_table[version-1][layer-1])[bitrate_enc]; if (bitrate == 0) // bitrate now in kbits per channel { print_err("### Illegal bitrate (0 kbits/channel) in MPEG level 2" " audio header\n"); return -1; } // sample rate field, whose meaning is dependent on version sampling_enc = (header >> 10) & 0x03; if (sampling_enc == 3) { print_err("### Illegal sampleing_enc (3) in MPEG layer 2 audio header\n"); return -1; } // sampling = sampling_table[version-1][sampling_enc]; // Make an AAC rate number from the rate number rate = (version * 3) + (sampling_enc & 2) + (sampling_enc == 0); // padding and private fields padding = (header >> 9) & 0x01; // private = (header >> 8) & 0x01; // private doesn't get used // mode and mode extension - these are also not used // Channel Mode // 00 - Stereo // 01 - Joint stereo (Stereo) // 10 - Dual channel (Stereo) // 11 - Single channel (Mono) // mode = (header >> 6) & 0x03; // modex = (header >> 4) & 0x03; // miscellaneous other things we ignore // copyright = (header >> 3) & 0x01; // original = (header >> 2) & 0x01; // emphasis = (header >> 0) & 0x03; // generate framesize and frame length // (for the moment, we only *use* the frame length) if (layer == 1) { // framesize = 384; // samples framelen = (12000 * bitrate / aud_frame_rate_n[rate] + padding) * 4; } else if (version == 1) { // framesize = 1152; // samples framelen = (144000 * bitrate / aud_frame_rate_n[rate] + padding); } else { // framesize = 576; // samples framelen = (72000 * bitrate / aud_frame_rate_n[rate] + padding); } return framelen; } /* * Build a new layer2 audio frame datastructure * * Returns 0 if all goes well, 1 if something goes wrong. */ static inline int build_audio_frame(audio_frame_p *frame) { audio_frame_p new = malloc(SIZEOF_AUDIO_FRAME); if (new == NULL) { print_err("### Unable to allocate audio frame datastructure\n"); return 1; } new->data = NULL; new->data_len = 0; *frame = new; return 0; } /* * Read the next audio frame. * * Assumes that the input stream is synchronised - i.e., it does not * try to cope if the next three bytes are not '1111 1111 1111'. * * - `file` is the file descriptor of the audio file to read from * - `frame` is the audio frame that is read * * Returns 0 if all goes well, EOF if end-of-file is read, and 1 if something * goes wrong. */ extern int read_next_l2audio_frame(int file, audio_frame_p *frame) { #define JUST_ENOUGH 6 // just enough to hold the bits of the headers we want int err, ii; byte header[JUST_ENOUGH]; byte *data = NULL; int frame_length; // XXXX Really 626.94 on average offset_t posn = tell_file(file); #if DEBUG fprint_msg("Offset: " OFFSET_T_FORMAT "\n",posn); #endif err = read_bytes(file,JUST_ENOUGH,header); if (err == EOF) return EOF; else if (err) { fprint_err("### Error reading header bytes of MPEG layer 2 audio frame\n" " (in frame starting at " OFFSET_T_FORMAT ")\n",posn); free(data); return 1; } #if DEBUG print_msg("MPEG layer 2 frame\n"); print_data(TRUE,"Start",header,JUST_ENOUGH,JUST_ENOUGH); #endif while (header[0] != 0xFF || (header[1] & 0xe0) != 0xe0) { int skip = JUST_ENOUGH; fprint_err("### MPEG layer 2 audio frame does not start with '1111 1111 111x'\n" " syncword - lost synchronisation?\n" " Found 0x%X%X%X instead of 0xFFE\n", (header[0] & 0xF0) >> 4, (header[0] & 0x0F), (header[1] & 0xe0) >> 4); fprint_err(" (in frame starting at " OFFSET_T_FORMAT ")\n",posn); do { err = read_bytes(file,1,header); skip++; if (err == 0 && header[0] == 0xff) { err = read_bytes(file,1,header + 1); skip++; if (err == 0 && (header[1] & 0xe0) == 0xe0) { err = read_bytes(file,JUST_ENOUGH - 2, header + 2); break; } } } while (!err); if (err) return 1; fprint_err("#################### Resuming after %d skipped bytes\n",skip); } frame_length = peek_frame_header((header[1] << 16) | (header[2] << 8) | header[3]); if (frame_length < 1) { print_err("### Bad MPEG layer 2 audio header\n"); return 1; } data = malloc(frame_length); if (data == NULL) { print_err("### Unable to extend data buffer for MPEG layer 2 audio frame\n"); free(data); return 1; } for (ii=0; iidata = data; (*frame)->data_len = frame_length; return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/l2audio_fns.h000066400000000000000000000041751261471605300172550ustar00rootroot00000000000000/* * Support for MPEG layer 2 audio streams. * * (actually, support for * * - MPEG-1 audio (described in ISO/IEC 11172-3), layers 1..3 * - MPEG-2 audio (described in ISO/IEC 13818-3), layer 2 * - unofficial MPEG-2.5 * * but MPEG-2 layer 2 is the main target) * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _l2audio_fns #define _l2audio_fns #include "audio_defns.h" /* * Tidy up and free an audio frame datastructure when we've finished with it * * Empties the datastructure, frees it, and sets `frame` to NULL. * * If `frame` is already NULL, does nothing. */ extern void free_audio_frame(audio_frame_p *frame); /* * Read the next audio frame. * * Assumes that the input stream is synchronised - i.e., it does not * try to cope if the next three bytes are not '1111 1111 1111'. * * - `file` is the file descriptor of the audio file to read from * - `frame` is the audio frame that is read * * Returns 0 if all goes well, EOF if end-of-file is read, and 1 if something * goes wrong. */ extern int read_next_l2audio_frame(int file, audio_frame_p *frame); #endif // _l2audio_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/m2ts2ts.c000066400000000000000000000320471261471605300163600ustar00rootroot00000000000000/* * Given an M2TS random access transport stream (BDAV MPEG-2 TS), * reorder the packets and strip off the time codes to give a normal * TS. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Kynesim Ltd, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ts_defns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" #define M2TS_PACKET_SIZE (4 + TS_PACKET_SIZE) struct _m2ts_packet_buffer { struct _m2ts_packet_buffer * next; struct _m2ts_packet_buffer * prev; uint32_t timestamp; byte * ts_packet; byte m2ts_packet[M2TS_PACKET_SIZE]; }; typedef struct _m2ts_packet_buffer *m2ts_packet_buffer_p; /* * Extract the timestamp from an M2TS packet and set up * the internal data structure pointers. */ static void parse_m2ts_packet(m2ts_packet_buffer_p packet_buffer) { packet_buffer->timestamp = (((uint32_t)(packet_buffer->m2ts_packet[0])) << 24) | (((uint32_t)(packet_buffer->m2ts_packet[1])) << 16) | (((uint32_t)(packet_buffer->m2ts_packet[2])) << 8) | ((uint32_t)(packet_buffer->m2ts_packet[3])); packet_buffer->ts_packet = packet_buffer->m2ts_packet + 4; packet_buffer->next = NULL; packet_buffer->prev = NULL; } /* * Read in M2TS packets, strip the timestamp, sort by timestamp and * write out TS packets. * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_packets(int input, FILE * output, const unsigned int reorder_buffer_size, int verbose, int quiet) { int err; m2ts_packet_buffer_p reorder_buffer_head = NULL; m2ts_packet_buffer_p reorder_buffer_tail = NULL; int reorder_buffer_entries = 0; m2ts_packet_buffer_p packet_buffer_in_hand = NULL; m2ts_packet_buffer_p packet_buffer; m2ts_packet_buffer_p p; int written; // For test purposes, just grab packets and print the time stamps while (1) { // Get a new packet buffer if (packet_buffer_in_hand != NULL) { packet_buffer = packet_buffer_in_hand; packet_buffer_in_hand = NULL; } else { packet_buffer = malloc(sizeof(struct _m2ts_packet_buffer)); /***DEBUG***/ fprint_msg("Allocated buffer @ %p\n", packet_buffer); if (packet_buffer == NULL) { print_err( "### m2ts2ts: out of memory allocating M2TS packet buffer\n"); while (reorder_buffer_head != NULL) { packet_buffer = reorder_buffer_head->next; free(reorder_buffer_head); reorder_buffer_head = packet_buffer; } if (packet_buffer_in_hand != NULL) free(packet_buffer_in_hand); return 1; } } err = read_bytes(input, M2TS_PACKET_SIZE, packet_buffer->m2ts_packet); if (err == EOF) { // End of file, no more to do, thank you and goodnight if (!quiet) print_msg("m2ts2ts: Reached end of file\n"); break; } else if (err) { // Badness has occurred, no point in saying more here while (reorder_buffer_head != NULL) { packet_buffer = reorder_buffer_head->next; free(reorder_buffer_head); reorder_buffer_head = packet_buffer; } if (packet_buffer_in_hand != NULL) free(packet_buffer_in_hand); return 1; } parse_m2ts_packet(packet_buffer); if (verbose) fprint_msg("Read timestamp 0x%08x\n", packet_buffer->timestamp); // Insert the packet in the reorder buffer, in time order // It's most likely that we'll get an up to date packet, // so start at the tail and work to the front p = reorder_buffer_tail; if (p != NULL) fprint_msg("tail timestamp = 0x%08x @ %p\n", p->timestamp, p); while (p != NULL && p->timestamp > packet_buffer->timestamp) { p = p->prev; if (p != NULL) fprint_msg("p timestamp = 0x%08x @ %p\n", p->timestamp, p); } if (p == NULL) { // Insert as the head of queue fprint_msg("### Insert %p at head: %p\n", packet_buffer, reorder_buffer_head); packet_buffer->next = reorder_buffer_head; reorder_buffer_head = packet_buffer; packet_buffer->prev = NULL; if (reorder_buffer_tail == NULL) { // I.e. this is the only entry on the queue reorder_buffer_tail = packet_buffer; } else { packet_buffer->next->prev = packet_buffer; } } else { // At this point, p points to the previous packet to our new one packet_buffer->next = p->next; p->next = packet_buffer; packet_buffer->prev = p; if (p == reorder_buffer_tail) { // I.e. we have inserted at the end reorder_buffer_tail = packet_buffer; } else { if (verbose) fprint_msg("Reordered packet timestamp=0x%08x\n", packet_buffer->timestamp); packet_buffer->next->prev = packet_buffer; } } fprint_msg("### packet at %p, prev=%p, next=%p\n", packet_buffer, (packet_buffer->prev), (packet_buffer->next)); reorder_buffer_entries++; if (reorder_buffer_entries > (int)reorder_buffer_size) { // Write out the head of the reorder buffer fprint_msg("### queue head @ %p, next=%p\n", reorder_buffer_head, (reorder_buffer_head->next)); packet_buffer = reorder_buffer_head; reorder_buffer_head = reorder_buffer_head->next; reorder_buffer_head->prev = NULL; written = fwrite(packet_buffer->ts_packet, TS_PACKET_SIZE, 1, output); if (written != 1) { // Major output catastrophe! fprint_err( "### m2ts2ts: Error writing TS packet: %s\n", strerror(errno)); free(packet_buffer); while (reorder_buffer_head != NULL) { packet_buffer = reorder_buffer_head->next; free(reorder_buffer_head); reorder_buffer_head = packet_buffer; } // No packet in hand here return 1; } reorder_buffer_entries--; if (verbose) fprint_msg("Written timestamp 0x%08x\n", packet_buffer->timestamp); packet_buffer_in_hand = packet_buffer; } } free(packet_buffer_in_hand); // Write out the remaining packets in the reorder buffer while (reorder_buffer_head != NULL) { packet_buffer = reorder_buffer_head->next; written = fwrite(reorder_buffer_head->ts_packet, TS_PACKET_SIZE, 1, output); if (written != 1) { // So close... fprint_err( "### m2ts2ts: Error writing final TS packets: %s\n", strerror(errno)); while (reorder_buffer_head != NULL) { packet_buffer = reorder_buffer_head->next; free(reorder_buffer_head); reorder_buffer_head = packet_buffer; } } free(reorder_buffer_head); reorder_buffer_head = packet_buffer; } return 0; } static void print_usage(void) { print_msg("Usage: m2ts2es [switches] [] []\n" "\n"); REPORT_VERSION("m2ts2ts"); print_msg("\n" "Files:\n" " is a BDAV MPEG-2 Transport Stream file (M2TS)\n" " (but see -stdin)\n" " is an H.222 Transport Stream file (but see -stdout)\n" "\n" "General Switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Input from standard input instead of a file\n" " -stdout Output to standard output instead of a file\n" " Forces -quiet and -err stderr.\n" " -verbose, -v Output informational/diagnostic messages\n" " -quiet, -q Only output error messages\n" " -buffer , -b Number of TS packets to buffer for reordering\n" " Defaults to 4.\n"); } int main(int argc, char *argv[]) { int use_stdout = FALSE; int use_stdin = FALSE; char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; int input = -1; // Our input file descriptor FILE *output = NULL; // Our output stream (if any) unsigned int reorder_buff_size = 4; // Number of TS packets to delay output int quiet = FALSE; // True => be as quiet as possible int verbose = FALSE; // True => output diagnostic messages int err = 0; int ii = 1; if (argc < 2) { print_usage(); return 0; } // Extract parameters while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help", argv[ii]) || !strcmp("-h", argv[ii]) || !strcmp("-help", argv[ii])) { print_usage(); return 0; } else if (!strcmp("-verbose", argv[ii]) || !strcmp("-v", argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet", argv[ii]) || !strcmp("-q", argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-buffer", argv[ii]) || !strcmp("-b", argv[ii])) { CHECKARG("m2ts2ts",ii); err = unsigned_value("m2ts2ts", argv[ii], argv[ii+1], 0, &reorder_buff_size); if (err) return 1; ii++; } else if (!strcmp("-stdin", argv[ii])) { use_stdin = TRUE; had_input_name = TRUE; // and it's "stdin"... } else if (!strcmp("-stdout", argv[ii])) { use_stdout = TRUE; had_output_name = TRUE; // ish redirect_output_stderr(); } else if (!strcmp("-err",argv[ii])) { CHECKARG("m2ts2ts",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### m2ts2ts: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else { fprint_err( "### m2ts2ts: " "Unrecognised command line switch '%s'\n", argv[ii]); return 1; } } else { if (had_input_name && had_output_name) { fprint_err( "### m2ts2ts: Unexpected '%s'\n", argv[ii]); return 1; } else if (had_input_name) // and not had_output_name, inc "-stdout" { output_name = argv[ii]; had_output_name = TRUE; } else // had_output_name && !had_input_name { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err( "### m2ts2ts: No input file specified\n"); return 1; } if (!had_output_name) { print_err( "### m2ts2ts: No output file specified\n"); return 1; } // Stop (as far as possible) extraneous data ending up in our output stream if (use_stdout) { verbose = FALSE; quiet = TRUE; } if (use_stdin) { input = STDIN_FILENO; } else { input = open_binary_file(input_name, FALSE); if (input == -1) { fprint_err( "### m2ts2ts: Unable to open input file %s\n", input_name); return 1; } } if (!quiet) fprint_msg("Reading from %s\n", (use_stdin ? "" : input_name)); if (use_stdout) { output = stdout; } else { output = fopen(output_name, "wb"); if (output == NULL) { if (!use_stdin) (void) close_file(input); fprint_err( "### m2ts2ts: Unable to open output file %s: %s\n", output_name, strerror(errno)); return 1; } } if (!quiet) fprint_msg("Writing to %s\n", (use_stdout ? "" : output_name)); err = extract_packets(input, output, reorder_buff_size, verbose, quiet); if (err) { print_err( "### m2ts2ts: Error extracting data\n"); if (!use_stdin) (void) close_file(input); if (!use_stdout) (void) fclose(output); return 1; } // Now tidy up if (!use_stdout) { errno = 0; err = fclose(output); if (err) { fprint_err( "### m2ts2ts: Error closing output file %s: %s\n", output_name, strerror(errno)); (void) close_file(input); return 1; } } if (!use_stdin) { err = close_file(input); if (err) fprint_err( "### m2ts2ts: Error closing input file %s\n", input_name); } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/misc.c000066400000000000000000001204311261471605300157700ustar00rootroot00000000000000/* * Miscellaneous useful functions. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include // For the command line utilities #include #include #include #include // O_... flags #ifdef _WIN32 #include #include #include #else // _WIN32 // For the socket handling #include #include #include #include // sockaddr_in #include // inet_aton #include // open, close #endif // _WIN32 #include "compat.h" #include "misc_fns.h" #include "es_fns.h" #include "pes_fns.h" #include "printing_fns.h" #define DEBUG_SEEK 1 // ============================================================ // CRC calculation // ============================================================ static uint32_t crc_table[256]; /* * Populate the (internal) CRC table. May safely be called more than once. */ static void make_crc_table(void) { int i, j; int already_done = 0; uint32_t crc; if (already_done) return; else already_done = 1; for (i = 0; i < 256; i++) { crc = i << 24; for (j = 0; j < 8; j++) { if (crc & 0x80000000L) crc = (crc << 1) ^ CRC32_POLY; else crc = ( crc << 1 ); } crc_table[i] = crc; } } /* * Compute CRC32 over a block of data, by table method. * * Returns a working value, suitable for re-input for further blocks * * Notes: Input value should be 0xffffffff for the first block, * else return value from previous call (not sure if that * needs complementing before being passed back in). */ extern uint32_t crc32_block(uint32_t crc, byte *pData, int blk_len) { static int table_made = FALSE; int i, j; if (!table_made) make_crc_table(); for (j = 0; j < blk_len; j++) { i = ((crc >> 24) ^ *pData++) & 0xff; crc = (crc << 8) ^ crc_table[i]; } return crc; } /* * Print out (the first `max`) bytes of a byte array. * * - if `is_msg` then print as a message, otherwise as an error * - `name` is identifying text to start the report with. * - `data` is the byte data to print. This may be NULL. * - `length` is its length * - `max` is the maximum number of bytes to print * * Prints out:: * * (): b1 b2 b3 b4 ... * * where no more than `max` bytes are to be printed (and "..." is printed * if not all bytes were shown). */ extern void print_data(int is_msg, const char *name, const byte data[], int length, int max) { int ii; if (length == 0) { fprint_msg_or_err(is_msg,"%s (0 bytes)\n",name); return; } #define MAX_LINE_LENGTH 80 fprint_msg_or_err(is_msg,"%s (%d byte%s):",name,length,(length==1?"":"s")); if (data == NULL) fprint_msg_or_err(is_msg," "); // Shouldn't happen, but let's be careful. else { for (ii = 0; ii < (length (): ... b1 b2 b3 b4 * * where no more than `max` bytes are to be printed (and "..." is printed * if not all bytes were shown). */ extern void print_end_of_data(char *name, byte data[], int length, int max) { int ii; if (length == 0) { fprint_msg("%s (0 bytes)\n",name); return; } fprint_msg("%s (%d byte%s):",name,length,(length==1?"":"s")); if (data == NULL) print_msg(" "); // Shouldn't happen, but let's be careful. else { if (max < length) print_msg(" ..."); for (ii = (length> (8-ii-1))); } } /* * Calculate log2 of `x` - for some reason this is missing from */ extern double log2(double x) { if (x == 2.0) return 1.0; else return log10(x) / log10(2); } // ============================================================ // Simple file I/O utilities // ============================================================ /* * Read a given number of bytes from a file. * * This is a jacket for `read`, allowing for the future possibility of * buffered input, and simplifying error handling. * * - `input` is the file descriptor for the file * - `num_bytes` is how many bytes to read * - `data` is the buffer to read the bytes into * * Returns 0 if all goes well, EOF if end of file was read, or 1 if some * other error occurred (in which case it will already have output a message * on stderr about the problem). */ extern int read_bytes(int input, int num_bytes, byte *data) { #ifdef _WIN32 int total = 0; int length; #else ssize_t total = 0; ssize_t length; #endif // Make some allowance for short reads - for instance, if we're reading // from a pipe and going just a bit faster than the sender while (total < num_bytes) { length = read(input,&(data[total]),num_bytes-total); if (length == 0) return EOF; else if (length == -1) { fprint_err("### Error reading %d bytes: %s\n",num_bytes, strerror(errno)); return 1; } total += length; } return 0; } /* * Utility function to seek within a file * * - `filedes` is the file to seek within * - `posn` is the position to which to seek * * This is a jacket for:: * * new_posn = lseek(filedes,posn,SEEK_SET); * * Returns 0 if all went well, 1 if the seek failed (either because * it returned -1, or because the position reached was not the position * requested). If an error occurs, then an explanatory message will * already have been written to stderr. */ extern int seek_file(int filedes, offset_t posn) { offset_t newposn = lseek(filedes,posn,SEEK_SET); if (newposn == -1) { fprint_err("### Error moving (seeking) to position " OFFSET_T_FORMAT " in file: %s\n",posn,strerror(errno)); return 1; } else if (newposn != posn) { fprint_err("### Error moving (seeking) to position " OFFSET_T_FORMAT " in file: actually moved to " OFFSET_T_FORMAT "\n",posn,newposn); return 1; } return 0; } /* * Utility function to report the current location within a file * * - `filedes` is the file to seek within * * This is a jacket for:: * * posn = lseek(filedes,0,SEEK_CUR); * * Returns the current position in the file if all went well, otherwise * -1 (in which case an error message will already have been written * on stderr) */ extern offset_t tell_file(int filedes) { #ifdef _WIN32 offset_t newposn = _tell(filedes); #else offset_t newposn = lseek(filedes,0,SEEK_CUR); #endif if (newposn == -1) fprint_err("### Error determining current position in file: %s\n", strerror(errno)); return newposn; } /* * Utility function to open a file (descriptor), and report any errors * * This is intended only for very simple usage, and is not mean to be * a general purpose "open" replacement. * * - `filename` is the name of the file to open * - `for_write` should be TRUE if the file is to be written to, * in which case it will be opened with flags O_WRONLY|O_CREAT|O_TRUNC, * or FALSE if the file is to be read, in which case it will be * opened with flag O_RDONLY. In both cases, on Windows the flag * O_BINARY will also be set. * * Returns the file descriptor for the file, or -1 if it failed to open * the file. */ extern int open_binary_file(char *filename, int for_write) { #ifdef _WIN32 int flags = O_BINARY; #else int flags = 0; #endif int filedes; if (for_write) { flags = flags | O_WRONLY | O_CREAT | O_TRUNC; filedes = open(filename,flags,00777); } else { flags = flags | O_RDONLY; filedes = open(filename,flags); } if (filedes == -1) fprint_err("### Error opening file %s for %s: %s\n", filename,(for_write?"write":"read"),strerror(errno)); return filedes; } /* * Utility function to close a file (descriptor), and report any errors * * Does nothing if filedes is -1 or STDIN_FILENO * * Returns 0 if all went well, 1 if an error occurred. */ extern int close_file(int filedes) { int err; if (filedes == -1 || filedes == STDIN_FILENO) return 0; err = close(filedes); if (err) { fprint_err("### Error closing file: %s\n",strerror(errno)); return 1; } else return 0; } // ============================================================ // More complex file I/O utilities // ============================================================ static int open_input_as_ES_using_PES(char *name, int quiet, int force_stream_type, int want_data, int *is_data, ES_p *es) { int err; PES_reader_p reader = NULL; if (name == NULL) { print_err("### Cannot use standard input to read PES\n"); return 1; } err = open_PES_reader(name,!quiet,!quiet,&reader); if (err) { fprint_err("### Error trying to build PES reader for input file %s\n",name); return 1; } err = build_elementary_stream_PES(reader,es); if (err) { fprint_err("### Error trying to build ES reader from PES reader\n" " for input file %s\n",name); (void) close_PES_reader(&reader); return 1; } if (!quiet) fprint_msg("Reading from %s\n",name); if (force_stream_type) { if (force_stream_type) *is_data = want_data; else *is_data = VIDEO_H262; if (!quiet) fprint_msg("Reading input as %s\n", (*is_data==VIDEO_H262?"MPEG-2 (H.262)": *is_data==VIDEO_H264?"MPEG-4/AVC (H.264)": *is_data==VIDEO_AVS ?"AVS": "???")); } else { *is_data = reader->video_type; } return 0; } static int open_input_as_ES_direct(char *name, int quiet, int force_stream_type, int want_data, int *is_data, ES_p *es) { int err; int use_stdin = (name == NULL); int input = -1; if (use_stdin) { input = STDIN_FILENO; } else { input = open_binary_file(name,FALSE); if (input == -1) return 1; } err = build_elementary_stream_file(input,es); if (err) { fprint_err("### Error building elementary stream for %s\n", use_stdin?"":name); if (!use_stdin) (void) close_file(input); return 1; } if (!quiet) fprint_msg("Reading from %s\n",(use_stdin?"":name)); if (force_stream_type || use_stdin) { if (force_stream_type) *is_data = want_data; else *is_data = VIDEO_H262; if (!quiet) fprint_msg("Reading input as %s\n", (*is_data==VIDEO_H262?"MPEG-2 (H.262)": *is_data==VIDEO_H264?"MPEG-4/AVC (H.264)": *is_data==VIDEO_AVS ?"AVS": "???")); } else { int video_type; err = decide_ES_video_type(*es,FALSE,FALSE,&video_type); if (err) { fprint_err("### Error deciding on stream type for file %s\n",name); close_elementary_stream(es); return 1; } // We want to rewind, to "unread" the bytes we read to decide our filetype. // The easiest way to do that and return to our initial conditions is to // recreate our ES context free_elementary_stream(es); err = seek_file(input,0); if (err) { print_err("### Error returning to start position in file after" " working out video type\n"); (void) close_file(input); return 1; } err = build_elementary_stream_file(input,es); if (err) { fprint_err("### Error (re)building elementary stream for %s\n",name); return 1; } *is_data = video_type; if (!quiet) fprint_msg("Input appears to be %s\n", (*is_data==VIDEO_H262?"MPEG-2 (H.262)": *is_data==VIDEO_H264?"MPEG-4/AVC (H.264)": *is_data==VIDEO_AVS?"AVS": *is_data==VIDEO_UNKNOWN?"Unknown": "???")); } return 0; } /* * Open an input file appropriately for reading as ES. * * - `name` is the name of the file, or NULL if standard input * is to be read from (which is not allowed if `use_pes` is * TRUE). * * - If `use_pes` is true then the input file is PS or TS and should * be read via a PES reader. * * - If `quiet` is true then information about the file being read will * not be written out. Otherwise, its name and what is decided about * its content will be printed. * * - If `force_stream_type` is true, then the caller asserts that * the input shall be read according to `want_data`, and not whatever * might be deduced from looking at the file itself. * * - If `force_stream_type` is true, then `want_data` should be one of * VIDEO_H262, VIDEO_H264 or VIDEO_AVS. `is_data` will then be * returned with the same value. * * - If `force_stream_type` is false, then the function will attempt * to determine what type of data it has, and `is_data` will be set * to whatever is determined (presumably one of VIDEO_H262, VIDEO_H264 * or VIDEO_AVS). It if cannot decide, then it will set it to VIDEO_UNKNOWN. * * - If input is from standard input, and `force_stream_type` is FALSE, * `is_data` will always be set to VIDEO_H262, which may be incorrect. * * - `es` is the new ES reader context. * * Returns 0 if all goes well, 1 if something goes wrong. In the latter case, * suitable messages will have been written out to standard error. */ extern int open_input_as_ES(char *name, int use_pes, int quiet, int force_stream_type, int want_data, int *is_data, ES_p *es) { if (use_pes) return open_input_as_ES_using_PES(name, quiet, force_stream_type, want_data, is_data, es); else return open_input_as_ES_direct(name, quiet, force_stream_type, want_data, is_data, es); } /* * Close an input ES stream opened with `open_input_as_ES`. * * Specifically, this will close the ES stream and also any underlying PES * reader and file (unless the input was standard input). * * - `name` is the name of the file, used for error reporting. * - `es` is the ES stream to close. This will be set to NULL. * * Returns 0 if all goes well, 1 if something goes wrong. In the latter case, * suitable messages will have been written out to standard error. */ extern int close_input_as_ES(char *name, ES_p *es) { if (!(*es)->reading_ES) { int err = close_PES_reader(&(*es)->reader); if (err) { fprint_err("### Error closing PES reader for file %s\n",name); close_elementary_stream(es); return 1; } } close_elementary_stream(es); return 0; } // ============================================================ // Command line "helpers" // ============================================================ /* * Read in an unsigned integer value, checking for extraneous characters. * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `str` is the string to read (typically ``argv[ii+1]``). * - `base` is the base to read to. If it is 0, then the user can use * C-style expressions like "0x68" to specify the base on the command line. * - `value` is the value read. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int unsigned_value(char *prefix, char *cmd, char *arg, int base, uint32_t *value) { char *ptr; unsigned long val; errno = 0; val = strtoul(arg,&ptr,base); if (errno) { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); if (errno == ERANGE && val == 0) fprint_err("String cannot be converted to (long) unsigned integer in %s %s\n", cmd,arg); else if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) fprint_err("Number is too big (overflows) in %s %s\n",cmd,arg); else fprint_err("Cannot read number in %s %s (%s)\n", cmd,arg,strerror(errno)); return 1; } if (ptr[0] != '\0') { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); if (ptr-arg == 0) fprint_err("Argument to %s should be a number, in %s %s\n", cmd,cmd,arg); else fprint_err("Unexpected characters ('%s') after the %.*s in %s %s\n", ptr, (int)(ptr-arg),arg, cmd,arg); return 1; } *value = val; return 0; } /* * Read in an integer value, checking for extraneous characters. * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `str` is the string to read (typically ``argv[ii+1]``). * - if `positive` is true, then the number read must be positive (0 or more). * - `base` is the base to read to. If it is 0, then the user can use * C-style expressions like "0x68" to specify the base on the command line. * - `value` is the value read. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int int_value(char *prefix, char *cmd, char *arg, int positive, int base, int *value) { char *ptr; long val; errno = 0; val = strtol(arg,&ptr,base); if (errno) { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); if (errno == ERANGE && val == 0) fprint_err("String cannot be converted to (long) integer in %s %s\n", cmd,arg); else if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) fprint_err("Number is too big (overflows) in %s %s\n",cmd,arg); else fprint_err("Cannot read number in %s %s (%s)\n", cmd,arg,strerror(errno)); return 1; } if (ptr[0] != '\0') { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); if (ptr-arg == 0) fprint_err("Argument to %s should be a number, in %s %s\n", cmd,cmd,arg); else fprint_err("Unexpected characters ('%s') after the %.*s in %s %s\n", ptr, (int)(ptr-arg),arg, cmd,arg); return 1; } if (val > INT_MAX || val < INT_MIN) { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); fprint_err("Value %ld (in %s %s) is too large (to fit into 'int')\n", val,cmd,arg); return 1; } if (positive && val < 0) { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); fprint_err("Value %ld (in %s %s) is less than zero\n", val,cmd,arg); return 1; } *value = val; return 0; } /* * Read in an integer value, checking for extraneous characters and a range. * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `str` is the string to read (typically ``argv[ii+1]``). * - `minimum` is the minimum value allowed. * - `maximum` is the maximum value allowed. * - `base` is the base to read to. If it is 0, then the user can use * C-style expressions like "0x68" to specify the base on the command line. * - `value` is the value read. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int int_value_in_range(char *prefix, char *cmd, char *arg, int minimum, int maximum, int base, int *value) { int err, temp; err = int_value(prefix,cmd,arg,(minimum >= 0),base,&temp); if (err) return err; if (temp > maximum || temp < minimum) { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); fprint_err("Value %d (in %s %s) is not in range %d..%d (0x%x..0x%x)\n", temp,cmd,arg,minimum,maximum,minimum,maximum); return 1; } *value = temp; return 0; } /* * Read in a double value, checking for extraneous characters. * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `str` is the string to read (typically ``argv[ii+1]``). * - if `positive` is true, then the number read must be positive (0 or more). * - `value` is the value read. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int double_value(char *prefix, char *cmd, char *arg, int positive, double *value) { char *ptr; double val; errno = 0; val = strtod(arg,&ptr); if (errno) { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); if (errno == ERANGE && val == 0) fprint_err("String cannot be converted to (double) float in %s %s\n", cmd,arg); else if (errno == ERANGE && (val == HUGE_VAL || val == -HUGE_VAL)) fprint_err("Number is too big (overflows) in %s %s\n",cmd,arg); else fprint_err("Cannot read number in %s %s (%s)\n", cmd,arg,strerror(errno)); return 1; } if (ptr[0] != '\0') { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); fprint_err("Unexpected characters ('%s') after the %.*s in %s %s\n", ptr, (int)(ptr-arg),arg, cmd,arg); return 1; } if (positive && val < 0) { print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); fprint_err("Value %f (in %s %s) is less than zero\n", val,cmd,arg); return 1; } *value = val; return 0; } /* * Read in a hostname and (optional) port * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. It may be NULL if we are reading a * "plain" host name, with no command switch in front of it. * - `arg` is the string to read (typically ``argv[ii+1]``). * - `hostname` is the host name read * - `port` is the port read (note that this is not touched if there is * no port number, so it may be set to a default before calling this * function) * * Note that this works by pointing `hostname` to the start of the `arg` * string, and then if there is a ':' in `arg`, changing that colon to * a '\0' delimiter, and interpreting the string thereafter as the port * number. If *that* fails, it resets the '\0' as a ':'. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int host_value(char *prefix, char *cmd, char *arg, char **hostname, int *port) { char *p = strchr(arg,':'); *hostname = arg; if (p != NULL) { char *ptr; p[0] = '\0'; // yep, modifying argv[ii+1] errno = 0; *port = strtol(p+1,&ptr,10); if (errno) { p[0] = ':'; print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); if (cmd) fprint_err("Cannot read port number in %s %s (%s)\n", cmd,arg,strerror(errno)); else fprint_err("Cannot read port number in %s (%s)\n", arg,strerror(errno)); return 1; } if (ptr[0] != '\0') { p[0] = ':'; print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); if (cmd) fprint_err("Unexpected characters in port number in %s %s\n", cmd,arg); else fprint_err("Unexpected characters in port number in %s\n",arg); return 1; } if (*port < 0) { p[0] = ':'; print_err("### "); if (prefix != NULL) fprint_err("%s: ",prefix); if (cmd) fprint_err("Negative port number in %s %s\n",cmd,arg); else fprint_err("Negative port number in %s\n",arg); return 1; } } return 0; } #ifdef _WIN32 // ============================================================ // WINDOWS32 specific socket stuff // ============================================================ /* * Start up WINSOCK so we can use sockets. * * Note that each successful call of this *must* be matched by a call * of winsock_cleanup(). * * Returns 0 if it works, 1 if it fails. */ extern int winsock_startup(void) { // The code herein is borrowed from the example in the Windows Sockets // Version 2: Platform DSK documentation for WSAStartup. WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2,2); err = WSAStartup(wVersionRequested,&wsaData); if (err != 0) { // We could not find a usable WinSock DLL print_err("### Unable to find a usable WinSock DLL\n"); return 1; } // Confirm that the WinSock DLL supports 2.2. // Note that if the DLL supports versions greater than 2.2 in addition to // 2.2, it will still return 2.2 in wVersion since that is the version we // requested. if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2 ) { fprint_err("### WinSock DLL was version %d.%d, not 2.2 or more\n", LOBYTE(wsaData.wVersion),HIBYTE(wsaData.wVersion)); WSACleanup(); return 1; } return 0; } /* * Convert a WinSock error number into a string and print it out on stderr */ extern void print_winsock_err(int err) { switch (err) { case WSABASEERR: print_err("(WSABASEERR) No Error"); break; case WSAEINTR: print_err("(WSAEINTR) Interrupted system call"); break; case WSAEBADF: print_err("(WSAEBADF) Bad file number"); break; case WSAEACCES: print_err("(WSAEACCES) Permission denied"); break; case WSAEFAULT: print_err("(WSAEFAULT) Bad address"); break; case WSAEINVAL: print_err("(WSAEINVAL) Invalid argument"); break; case WSAEMFILE: print_err("(WSAEMFILE) Too many open files"); break; case WSAEWOULDBLOCK: print_err("(WSAEWOULDBLOCK) Operation would block"); break; case WSAEINPROGRESS: print_err("(WSAEINPROGRESS) A transaction is still in progress"); break; case WSAEALREADY: print_err("(WSAEALREADY) Operation already in progress"); break; case WSAENOTSOCK: print_err("(WSAENOTSOCK) Socket operation on non-socket"); break; case WSAEDESTADDRREQ: print_err("(WSAEDESTADDRREQ) Destination address required"); break; case WSAEMSGSIZE: print_err("(WSAEMSGSIZE) Message too long"); break; case WSAEPROTOTYPE: print_err("(WSAEPROTOTYPE) Protocol wrong type for socket"); break; case WSAENOPROTOOPT: print_err("(WSAENOPROTOOPT) Bad protocol option"); break; case WSAEPROTONOSUPPORT: print_err("(WSAEPROTONOSUPPORT) Protocol not supported"); break; case WSAESOCKTNOSUPPORT: print_err("(WSAESOCKTNOSUPPORT) Socket type not supported"); break; case WSAEOPNOTSUPP: print_err("(WSAEOPNOTSUPP) Operation not supported on socket"); break; case WSAEPFNOSUPPORT: print_err("(WSAEPFNOSUPPORT) Protocol family not supported"); break; case WSAEAFNOSUPPORT: print_err("(WSAEAFNOSUPPORT) Address family not supported by protocol family"); break; case WSAEADDRINUSE: print_err("(WSAEADDRINUSE) Address already in use"); break; case WSAEADDRNOTAVAIL: print_err("(WSAEADDRNOTAVAIL) Can't assign requested address"); break; case WSAENETDOWN: print_err("(WSAENETDOWN) Network is down"); break; case WSAENETUNREACH: print_err("(WSAENETUNREACH) Network is unreachable"); break; case WSAENETRESET: print_err("(WSAENETRESET) Net dropped connection or reset"); break; case WSAECONNABORTED: print_err("(WSAECONNABORTED) Software caused connection abort"); break; case WSAECONNRESET: print_err("(WSAECONNRESET) Connection reset by peer"); break; case WSAENOBUFS: print_err("(WSAENOBUFS) No buffer space available"); break; case WSAEISCONN: print_err("(WSAEISCONN) Socket is already connected"); break; case WSAENOTCONN: print_err("(WSAENOTCONN) Socket is not connected"); break; case WSAESHUTDOWN: print_err("(WSAESHUTDOWN) Can't send after socket shutdown"); break; case WSAETOOMANYREFS: print_err("(WSAETOOMANYREFS) Too many references, can't splice"); break; case WSAETIMEDOUT: print_err("(WSAETIMEDOUT) Connection timed out"); break; case WSAECONNREFUSED: print_err("(WSAECONNREFUSED) Connection refused"); break; case WSAELOOP: print_err("(WSAELOOP) Too many levels of symbolic links"); break; case WSAENAMETOOLONG: print_err("(WSAENAMETOOLONG) File name too long"); break; case WSAEHOSTDOWN: print_err("(WSAEHOSTDOWN) Host is down"); break; case WSAEHOSTUNREACH: print_err("(WSAEHOSTUNREACH) No Route to Host"); break; case WSAENOTEMPTY: print_err("(WSAENOTEMPTY) Directory not empty"); break; case WSAEPROCLIM: print_err("(WSAEPROCLIM) Too many processes"); break; case WSAEUSERS: print_err("(WSAEUSERS) Too many users"); break; case WSAEDQUOT: print_err("(WSAEDQUOT) Disc Quota Exceeded"); break; case WSAESTALE: print_err("(WSAESTALE) Stale NFS file handle"); break; case WSASYSNOTREADY: print_err("(WSASYSNOTREADY) Network SubSystem is unavailable"); break; case WSAVERNOTSUPPORTED: print_err("(WSAVERNOTSUPPORTED) WINSOCK DLL Version out of range"); break; case WSANOTINITIALISED: print_err("(WSANOTINITIALISED) Successful WSASTARTUP not yet performed"); break; case WSAEREMOTE: print_err("(WSAEREMOTE) Too many levels of remote in path"); break; case WSAHOST_NOT_FOUND: print_err("(WSAHOST_NOT_FOUND) Host not found"); break; case WSATRY_AGAIN: print_err("(WSATRY_AGAIN) Non-Authoritative Host not found"); break; case WSANO_RECOVERY: print_err("(WSANO_RECOVERY) Non-Recoverable errors: FORMERR, REFUSED, NOTIMP"); break; case WSANO_DATA: print_err("(WSANO_DATA) Valid name, no data record of requested type"); break; default: fprint_err("winsock error %d",err); break; } } /* * Clean up WINSOCK after we've used sockets. * * Returns 0 if it works, 1 if it fails. */ static int winsock_cleanup(void) { int err = WSACleanup(); if (err != 0) { err = WSAGetLastError(); print_err("### Error cleaning up WinSock: "); print_winsock_err(err); print_err("\n"); return 1; } return 0; } #endif // ============================================================ // Socket support // ============================================================ /* * Connect to a socket, to allow us to write to it, using TCP/IP. * * - `hostname` is the name of the host to connect to * - `port` is the port to use * - if `use_tcpip`, then a TCP/IP connection will be made, otherwise UDP. * For UDP, multicast TTL will be enabled. * - If the destination address (`hostname`) is multicast and `multicast_ifaddr` * is supplied, it is used to select (by IP address) the network interface * on which to send the multicasts. It may be NULL to use the default, * or for non-multicast cases. * * A socket connected to via this function must be disconnected from with * disconnect_socket(). * * (This is actually only crucial on Windows, where WinSock must be * neatly shut down, but should also be done on Unix in case future * termination code is added.) * * Returns a positive integer (the file descriptor for the socket) if it * succeeds, or -1 if it fails, in which case it will have complained on * stderr. */ extern int connect_socket(char *hostname, int port, int use_tcpip, char *multicast_ifaddr) { #ifdef _WIN32 SOCKET output; #else // _WIN32 int output; #endif // _WIN32 int result; struct hostent *hp; struct sockaddr_in ipaddr; #ifdef _WIN32 int err = winsock_startup(); if (err) return 1; #endif output = socket(AF_INET, (use_tcpip?SOCK_STREAM:SOCK_DGRAM), 0); #ifdef _WIN32 if (output == INVALID_SOCKET) { err = WSAGetLastError(); print_err("### Unable to create socket: "); print_winsock_err(err); print_err("\n"); return -1; } #else // _WIN32 if (output == -1) { fprint_err("### Unable to create socket: %s\n",strerror(errno)); return -1; } #endif // _WIN32 #if _WIN32 // On Windows, apparently, gethostbyname will not work for numeric IP addresses. // The clever solution would be to move to using getaddrinfo for all forms of // host address, but the simpler solution is just to do: { unsigned long addr = inet_addr(hostname); if (addr != INADDR_NONE) // i.e., success { ipaddr.sin_addr.s_addr = addr; ipaddr.sin_family = AF_INET; } else { hp = gethostbyname(hostname); if (hp == NULL) { err = WSAGetLastError(); fprint_err("### Unable to resolve host %s: ",hostname); print_winsock_err(err); print_err("\n"); return -1; } memcpy(&ipaddr.sin_addr.s_addr, hp->h_addr, hp->h_length); ipaddr.sin_family = hp->h_addrtype; } } ipaddr.sin_port = htons(port); #else // _WIN32 hp = gethostbyname(hostname); if (hp == NULL) { fprint_err("### Unable to resolve host %s: %s\n", hostname,hstrerror(h_errno)); return -1; } memcpy(&ipaddr.sin_addr.s_addr, hp->h_addr, hp->h_length); ipaddr.sin_family = hp->h_addrtype; #if !defined(__linux__) // On BSD, the length is defined in the datastructure ipaddr.sin_len = sizeof(struct sockaddr_in); #endif // __linux__ ipaddr.sin_port = htons(port); #endif // _WIN32 if (IN_CLASSD(ntohl(ipaddr.sin_addr.s_addr))) { // Needed if we're doing multicast byte ttl = 16; result = setsockopt(output, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl)); #ifdef _WIN32 if (result == SOCKET_ERROR) { err = WSAGetLastError(); print_err("### Error setting socket for IP_MULTICAST_TTL: "); print_winsock_err(err); print_err("\n"); return -1; } #else // _WIN32 if (result < 0) { fprint_err("### Error setting socket for IP_MULTICAST_TTL: %s\n", strerror(errno)); return -1; } #endif // _WIN32 if (multicast_ifaddr) { #ifdef _WIN32 unsigned long addr; print_err("!!! Specifying the multicast interface is not supported on " "some versions of Windows\n"); // Also, choosing an invalid address is not (may not be) detected on Windows addr = inet_addr(multicast_ifaddr); if (addr == INADDR_NONE) { err = WSAGetLastError(); fprint_err("### Error translating '%s' as a dotted IP address: ", multicast_ifaddr); print_winsock_err(err); print_err("\n"); return -1; } #else // _WIN32 struct in_addr addr; inet_aton(multicast_ifaddr, &addr); #endif // _WIN32 result = setsockopt(output,IPPROTO_IP,IP_MULTICAST_IF, (char *)&addr,sizeof(addr)); #ifdef _WIN32 if (result == SOCKET_ERROR) { err = WSAGetLastError(); fprint_err("### Unable to set multicast interface %s: "); print_winsock_err(err); print_err("\n"); return -1; } #else // _WIN32 if (result < 0) { fprint_err("### Unable to set multicast interface %s: %s\n", multicast_ifaddr,strerror(errno)); return -1; } #endif // _WIN32 } } result = connect(output,(struct sockaddr*)&ipaddr,sizeof(ipaddr)); #ifdef _WIN32 if (result == SOCKET_ERROR) { err = WSAGetLastError(); fprint_err("### Unable to connect to host %s: ",hostname); print_winsock_err(err); print_err("\n"); return -1; } #else // _WIN32 if (result < 0) { fprint_err("### Unable to connect to host %s: %s\n", hostname,strerror(errno)); return -1; } #endif // _WIN32 return output; } /* * Disconnect from a socket (close it). * * Returns 0 if all goes well, 1 otherwise. */ #ifdef _WIN32 extern int disconnect_socket(SOCKET socket) { int err = closesocket(socket); if (err != 0) { err = WSAGetLastError(); print_err("### Error closing output: "); print_winsock_err(err); print_err("\n"); return 1; } err = winsock_cleanup(); if (err) return 1; return 0; } #else // _WIN32 extern int disconnect_socket(int socket) { int err = close(socket); if (err == EOF) { fprint_err("### Error closing output: %s\n",strerror(errno)); return 1; } return 0; } #endif // _WIN32 const char *ipv4_addr_to_string(const uint32_t addr) { static char buf[64]; snprintf(buf, sizeof(buf), "%d.%d.%d.%d", (addr >> 24)&0xff, (addr >> 16)&0xff, (addr >> 8)&0xff, (addr & 0xff)); return buf; } int ipv4_string_to_addr(uint32_t *dest, const char *string) { char *str_cpy = strdup(string); int rv =0; char *p, *p2; int val; int nr; uint32_t out = 0; for (nr = 0,p = str_cpy; nr < 4 && *p; p = p2+1, ++nr) { char *px = NULL; p2 = strchr(p, '.'); if (p2) { *p2 = '\0'; } val = strtoul(p, &px, 0); if (px && *px) { return -1; } out |= (val << ((3-nr)<<3)); } (*dest) = out; free(str_cpy); return rv; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/misc_defns.h000066400000000000000000000042211261471605300171520ustar00rootroot00000000000000/* * Miscellaneous useful definitions * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _misc_defns #define _misc_defns #include "tswrite_defns.h" #include "video_defns.h" // Some (internal) functions find it convenient to have a union of the // possible output streams. Rather than duplicate the definition of these, // we put them here... union _writer { FILE *es_output; // output to an ES file TS_writer_p ts_output; // output via a TS writer }; typedef union _writer WRITER; // In the programs that handle command lines, it's useful to have a simple // macro for checking the presence of subsidiary arguments. // Assumes that argc and argv have their normal names. #define CHECKARG(program,argno) \ if ((argno)+1 == argc) \ { \ fprintf(stderr,"### %s: missing argument to %s\n",program,argv[(argno)]); \ return 1; \ } // A simple macro to return a bit from a bitfield, for use in printf() #define ON(byt,msk) ((byt & msk)?1:0) #endif // _misc_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/misc_fns.h000066400000000000000000000413351261471605300166500ustar00rootroot00000000000000/* * Miscellaneous useful functions. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _misc_fns #define _misc_fns #include "misc_defns.h" #include "es_defns.h" #ifndef _WIN32 #include #endif #ifdef _WIN32 #include "compat.h" #include #endif // _WIN32 #define CRC32_POLY 0x04c11db7L /* * Compute CRC32 over a block of data, by table method. * * Returns a working value, suitable for re-input for further blocks * * Notes: Input value should be 0xffffffff for the first block, * else return value from previous call (not sure if that * needs complementing before being passed back in). */ extern uint32_t crc32_block(uint32_t crc, byte *pData, int blk_len); /* * Print out the bottom N bits from a byte */ extern void print_bits(int num_bits, byte value); /* * Print out (the first `max`) bytes of a byte array. * * - if `is_msg` then print as a message, otherwise as an error * - `name` is identifying text to start the report with. * - `data` is the byte data to print. This may be NULL. * - `length` is its length * - `max` is the maximum number of bytes to print * * Prints out:: * * (): b1 b2 b3 b4 ... * * where no more than `max` bytes are to be printed (and "..." is printed * if not all bytes were shown). */ extern void print_data(int is_msg, const char *name, const byte data[], int length, int max); /* * Print out (the last `max`) bytes of a byte array. * * - `name` is identifying text to start the report with. * - `data` is the byte data to print. This may be NULL. * - `length` is its length * - `max` is the maximum number of bytes to print * * Prints out:: * * (): ... b1 b2 b3 b4 * * where no more than `max` bytes are to be printed (and "..." is printed * if not all bytes were shown). */ extern void print_end_of_data(char *name, byte data[], int length, int max); /* * Calculate log2 of `x` - for some reason this is missing from */ extern double log2(double x); // ============================================================ // Simple file I/O utilities // ============================================================ /* * Read a given number of bytes from a file. * * This is a jacket for `read`, allowing for the future possibility of * buffered input, and simplifying error handling. * * - `input` is the file descriptor for the file * - `num_bytes` is how many bytes to read * - `data` is the buffer to read the bytes into * * Returns 0 if all goes well, EOF if end of file was read, or 1 if some * other error occurred (in which case it will already have output a message * on stderr about the problem). */ extern int read_bytes(int input, int num_bytes, byte *data); /* * Utility function to seek within a file * * - `filedes` is the file to seek within * - `posn` is the position to which to seek * * This is a jacket for:: * * new_posn = lseek(filedes,posn,SEEK_SET); * * Returns 0 if all went well, 1 if the seek failed (either because * it returned -1, or because the position reached was not the position * requested). If an error occurs, then an explanatory message will * already have been written to stderr. */ extern int seek_file(int filedes, offset_t posn); /* * Utility function to report the current location within a file * * - `filedes` is the file to seek within * * This is a jacket for:: * * posn = lseek(filedes,0,SEEK_CUR); * * Returns the current position in the file if all went well, otherwise * -1 (in which case an error message will already have been written * on stderr) */ extern offset_t tell_file(int filedes); /* * Utility function to open a file (descriptor), and report any errors * * This is intended only for very simple usage, and is not mean to be * a general purpose "open" replacement. * * - `filename` is the name of the file to open * - `for_write` should be TRUE if the file is to be written to, * in which case it will be opened with flags O_WRONLY|O_CREAT|O_TRUNC, * or FALSE if the file is to be read, in which case it will be * opened with flag O_RDONLY. In both cases, on Windows the flag * O_BINARY will also be set. * * Returns the file descriptor for the file, or -1 if it failed to open * the file. */ extern int open_binary_file(char *filename, int for_write); /* * Utility function to close a file (descriptor), and report any errors * * Returns 0 if all went well, 1 if an error occurred. */ extern int close_file(int filedes); // ============================================================ // More complex file I/O utilities // ============================================================ /* * Open an input file appropriately for reading as ES. * * - `name` is the name of the file, or NULL if standard input * is to be read from (which is not allowed if `use_pes` is * TRUE). * * - If `use_pes` is true then the input file is PS or TS and should * be read via a PES reader. * * - If `quiet` is true then information about the file being read will * not be written out. Otherwise, its name and what is decided about * its content will be printed. * * - If `force_stream_type` is true, then the caller asserts that * the input shall be read according to `want_data`, and not whatever * might be deduced from looking at the file itself. * * - If `force_stream_type` is true, then `want_data` should be one of * VIDEO_H262, VIDEO_H264 or VIDEO_AVS. `is_data` will then be * returned with the same value. * * - If `force_stream_type` is false, then the function will attempt * to determine what type of data it has, and `is_data` will be set * to whatever is determined (presumably one of VIDEO_H262, VIDEO_H264 * or VIDEO_AVS). * * - If input is from standard input, and `force_stream_type` is FALSE, * `is_data` will always be set to VIDEO_H262, which may be incorrect. * * - `es` is the new ES reader context. * * Returns 0 if all goes well, 1 if something goes wrong. In the latter case, * suitable messages will have been written out to standard error. */ extern int open_input_as_ES(char *name, int use_pes, int quiet, int force_stream_type, int want_data, int *is_data, ES_p *es); /* * Close an input ES stream opened with `open_input_as_ES`. * * Specifically, this will close the ES stream and also any underlying PES * reader and file (unless the input was standard input). * * - `name` is the name of the file, used for error reporting. * - `es` is the ES stream to close. This will be set to NULL. * * Returns 0 if all goes well, 1 if something goes wrong. In the latter case, * suitable messages will have been written out to standard error. */ extern int close_input_as_ES(char *name, ES_p *es); // ============================================================ // Command line "helpers" // ============================================================ /* * Read in an unsigned integer value, checking for extraneous characters. * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `str` is the string to read (typically ``argv[ii+1]``). * - `base` is the base to read to. If it is 0, then the user can use * C-style expressions like "0x68" to specify the base on the command line. * - `value` is the value read. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int unsigned_value(char *prefix, char *cmd, char *arg, int base, uint32_t *value); /* * Read in an integer value, checking for extraneous characters. * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `str` is the string to read (typically ``argv[ii+1]``). * - if `positive` is true, then the number read must be positive (0 or more). * - `base` is the base to read to. If it is 0, then the user can use * C-style expressions like "0x68" to specify the base on the command line. * - `value` is the value read. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int int_value(char *prefix, char *cmd, char *str, int positive, int base, int *value); /* * Read in an integer value, checking for extraneous characters and a range. * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `str` is the string to read (typically ``argv[ii+1]``). * - `minimum` is the minimum value allowed. * - `maximum` is the maximum value allowed. * - `base` is the base to read to. If it is 0, then the user can use * C-style expressions like "0x68" to specify the base on the command line. * - `value` is the value read. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int int_value_in_range(char *prefix, char *cmd, char *arg, int minimum, int maximum, int base, int *value); /* * Read in a double value, checking for extraneous characters. * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `str` is the string to read (typically ``argv[ii+1]``). * - if `positive` is true, then the number read must be positive (0 or more). * - `value` is the value read. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int double_value(char *prefix, char *cmd, char *arg, int positive, double *value); /* * Read in a hostname and (optional) port * * - `prefix` is an optional prefix for error messages, typically the * name of the program. It may be NULL. * - `cmd` is the command switch we're reading for (typically ``argv[ii]``), * which is used in error messages. * - `arg` is the string to read (typically ``argv[ii+1]``). * - `hostname` is the host name read * - `port` is the port read (note that this is not touched if there is * no port number, so it may be set to a default before calling this * function) * * Note that this works by pointing `hostname` to the start of the `arg` * string, and then if there is a ':' in `arg`, changing that colon to * a '\0' delimiter, and interpreting the string thereafter as the port * number. If *that* fails, it resets the '\0' as a ':'. * * Returns 0 if all went well, 1 otherwise (in which case a message * explaining will have been written to stderr). */ extern int host_value(char *prefix, char *cmd, char *arg, char **hostname, int *port); // ============================================================ // Sockets // ============================================================ #ifdef _WIN32 /* * Start up WINSOCK so we can use sockets. * * Note that each successful call of this *must* be matched by a call * of winsock_cleanup(). * * Returns 0 if it works, 1 if it fails. */ extern int winsock_startup(void); /* * Convert a WinSock error number into a string and print it out on stderr */ extern void print_winsock_err(int err); #endif // _WIN32 /* * Connect to a socket, to allow us to write to it, using TCP/IP. * * - `hostname` is the name of the host to connect to * - `port` is the port to use * - if `use_tcpip`, then a TCP/IP connection will be made, otherwise UDP. * For UDP, multicast TTL will be enabled. * - If the destination address (`hostname`) is multicast and `multicast_ifaddr` * is supplied, it is used to select (by IP address) the network interface * on which to send the multicasts. It may be NULL to use the default, * or for non-multicast cases. * * A socket connected to via this function must be disconnected from with * disconnect_socket(). * * Returns a positive integer (the file descriptor for the socket) if it * succeeds, or -1 if it fails, in which case it will have complained on * stderr. */ extern int connect_socket(char *hostname, int port, int use_tcpip, char *multicast_ifaddr); /* * Disconnect from a socket (close it). * * Returns 0 if all goes well, 1 otherwise. */ #ifdef _WIN32 extern int disconnect_socket(SOCKET socket); #else // _WIN32 extern int disconnect_socket(int socket); #endif // _WIN32 /*! Decode a host byte order address to a static buffer. */ const char *ipv4_addr_to_string(const uint32_t addr); /*! Decode a string to a host byte order address */ int ipv4_string_to_addr(uint32_t *dest, const char *addr); // ============================================================ // Byte order handling // ============================================================ static inline uint32_t uint_32_be(const uint8_t *const p) { return (((int)p[0]&0xff) << 24) | (((int)p[1]&0xff) << 16) | (((int)p[2]&0xff) << 8) | (((int)p[3]&0xff)); } static inline uint32_t uint_32_le(const uint8_t *const p) { return (((int)p[0]&0xff) | (((int)p[1]&0xff) << 8) | (((int)p[2]&0xff) << 16) | (((int)p[3]&0xff) << 24)); } static inline uint16_t uint_16_be(const uint8_t *const p) { return ((((int)p[0])&0xff)<<8) | ((((int)p[1])&0xff)); } static inline uint16_t uint_16_le(const uint8_t *const p) { return (((int)p[0]&0xff) | ((int)p[1]&0xff)<<8); } // ============================================================ // Time diffs // ============================================================ #define PCR_UNSIGNED_WRAP (300ULL * (1ULL << 33)) // (x - y) with allowance for PCR wrap - unsigned version static inline uint64_t pcr_unsigned_diff(uint64_t x, uint64_t y) { return x > y ? x - y : 300ULL * (1ULL << 33) - (y - x); } #define PCR_SIGNED_WRAP (300LL * (1LL << 33)) #define PCR_SIGNED_MAX (PCR_SIGNED_WRAP / 2LL - 1LL) #define PCR_SIGNED_MIN (-PCR_SIGNED_WRAP / 2LL) // (x - y) with allowance for PCR wrap - signed version static inline int64_t pcr_signed_diff(uint64_t x, uint64_t y) { int64_t r = x - y; return r < PCR_SIGNED_MIN ? r + PCR_SIGNED_WRAP : r > PCR_SIGNED_MAX ? r - PCR_SIGNED_WRAP : r; } // Deal with simple overflow static inline uint64_t pcr_unsigned_wrap(uint64_t x) { return x >= PCR_UNSIGNED_WRAP ? x - PCR_UNSIGNED_WRAP : x; } static inline int64_t pts_signed_diff(uint64_t x, uint64_t y) { int64_t r = x - y; return (r << 31) >> 31; } #endif // _misc_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/nalunit.c000066400000000000000000001746251261471605300165250ustar00rootroot00000000000000/* * Utilities for working with NAL units in H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "printing_fns.h" #include "es_fns.h" #include "ts_fns.h" #include "bitdata_fns.h" #include "nalunit_fns.h" #include "misc_fns.h" #include "printing_fns.h" #define DEBUG 0 #define REPORT_NAL_SHOWS_ADDRESS 0 /* * Request details of the NAL unit contents as they are read */ extern void set_show_nal_reading_details(nal_unit_context_p context, int show) { context->show_nal_details = show; } // ------------------------------------------------------------ // NAL unit context // ------------------------------------------------------------ /* * Build a new NAL unit context, for reading NAL units from an ES. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_nal_unit_context(ES_p es, nal_unit_context_p *context) { int err; nal_unit_context_p new = malloc(SIZEOF_NAL_UNIT_CONTEXT); if (new == NULL) { print_err("### Unable to allocate NAL unit context datastructure\n"); return 1; } new->es = es; new->count = 0; new->show_nal_details = FALSE; err = build_param_dict(&new->seq_param_dict); if (err) { free(new); return err; } err = build_param_dict(&new->pic_param_dict); if (err) { free_param_dict(&new->seq_param_dict); free(new); return err; } *context = new; return 0; } /* * Free a NAL unit context datastructure. * * Clears the datastructure, frees it, and returns `context` as NULL. * * Does nothing if `context` is already NULL. */ extern void free_nal_unit_context(nal_unit_context_p *context) { nal_unit_context_p cc = *context; if (cc == NULL) return; free_param_dict(&cc->seq_param_dict); free_param_dict(&cc->pic_param_dict); free(*context); *context = NULL; return; } /* * Rewind a file being read as NAL units. * * A thin jacket for `seek_ES`. * * Doesn't unset the sequence and picture parameter dictionaries * that have been built up when reading the file - this may possibly * not be the desired behaviour, but should be OK for well behaved files. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int rewind_nal_unit_context(nal_unit_context_p context) { ES_offset start_of_file = {0,0}; return seek_ES(context->es,start_of_file); } // ------------------------------------------------------------ // Basic NAL unit datastructure stuff // ------------------------------------------------------------ /* * Build a new NAL unit datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_nal_unit(nal_unit_p *nal) { int err; nal_unit_p new = malloc(SIZEOF_NAL_UNIT); if (new == NULL) { print_err("### Unable to allocate NAL unit datastructure\n"); return 1; } err = setup_ES_unit(&(new->unit)); if (err) { print_err("### Unable to allocate NAL unit data buffer\n"); free(new); return 1; } // However, we haven't yet got any actual data new->data = NULL; // Only set to unit.data[3] when we *have* a NAL unit new->data_len = 0; new->rbsp = NULL; new->rbsp_len = 0; new->bit_data = NULL; new->nal_unit_type = NAL_UNSPECIFIED; new->starts_picture_decided = FALSE; new->starts_picture = FALSE; new->start_reason = NULL; new->decoded = FALSE; *nal = new; return 0; } /* * Tidy up a NAL unit datastructure after we've finished with it. */ static inline void clear_nal_unit(nal_unit_p nal) { clear_ES_unit(&(nal->unit)); nal->data = NULL; nal->data_len = 0; if (nal->rbsp != NULL) { free(nal->rbsp); nal->rbsp_len = 0; } free_bitdata(&nal->bit_data); } /* * Tidy up and free a NAL unit datastructure after we've finished with it. * * Empties the NAL unit datastructure, frees it, and sets `nal` to NULL. * * If `nal` is already NULL, does nothing. */ extern void free_nal_unit(nal_unit_p *nal) { if (*nal == NULL) return; clear_nal_unit(*nal); free(*nal); *nal = NULL; } // ------------------------------------------------------------ // Interpretive functions // ------------------------------------------------------------ /* * Process `data` to remove any emulation prevention bytes. * * That is, sort out 0x000003 sequences. * * Basically, if the data stream was *meant* to contain 0x0000xx, * then it will instead contain 0x000003xx, and so we need to remove * that extra 0x03 (in fact, there are also rules about what values * 0xxx can take, but we shall assume that they have been followed * by the encoder). See H.264 7.3.1 * * - `data` is the NAL unit data array, including the first byte, * which contains the nal_ref_idc and nal_unit_type. * - `rbsp` is the processed data, not including said first byte. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int remove_emulation_prevention(byte data[], int data_len, byte *rbsp[], int *rbsp_len) { int ii; int posn = 0; byte prev1 = 27; // J. Random Number byte prev2 = 27; byte *tgt = NULL; // We know we're going to produce data that is no longer than our input tgt = malloc(data_len); if (tgt == NULL) { print_err("### Cannot malloc RBSP target array\n"); return 1; } for (ii=1; iibit_data != NULL) return 0; // Only remove the emulation 03 bytes when we think we need to // (of course, we *could* do this as part of the bitdata byte // reading code, but unless/until it's clear that the tradeoff // in time/complexity is worth it, let's not bother). if (nal->rbsp == NULL) { err = remove_emulation_prevention(nal->data,nal->data_len, &(nal->rbsp),&(nal->rbsp_len)); if (err) { print_err("### Error removing emulation prevention bytes\n"); return 1; } } err = build_bitdata(&bd,nal->rbsp,nal->rbsp_len); if (err) { print_err("### Unable to build bitdata datastructure for NAL RBSP\n"); return 1; } nal->bit_data = bd; return 0; } /* * Look at the start of the slice header. * * Assumes that this *is* a NAL unit representing a slice. * * Don't call this directly - call read_rbsp_data() instead. * * If either of `seq_param_dict` or `pic_param_dict` is NULL, then * we only read the first few entries in the RBSP (including the * slice_type and the pic_parameter_set_id). * * Returns 0 if it succeeds, 1 if some error occurs. */ static int read_slice_data(nal_unit_p nal, param_dict_p seq_param_dict, param_dict_p pic_param_dict, int show_nal_details) { int err; bitdata_p bd = nal->bit_data; uint32_t temp; nal_slice_data_p data = &(nal->u.slice); nal_seq_param_data_p seq_param_data = NULL; nal_pic_param_data_p pic_param_data = NULL; #define CHECK(name) \ if (err) \ { \ fprint_err("### Error reading %s field from slice data\n",(name)); \ } err = read_exp_golomb(bd,&data->first_mb_in_slice); CHECK("first_mb_in_slice"); err = read_exp_golomb(bd,&data->slice_type); CHECK("slice_type"); err = read_exp_golomb(bd,&temp); CHECK("pic_parameter_set_id"); data->pic_parameter_set_id = temp; if (show_nal_details) { fprint_msg("@@ NAL " OFFSET_T_FORMAT_08 "/%04d: size %d\n" " nal_ref_idc %x nal_unit_type %02x (%s)\n", nal->unit.start_posn.infile,nal->unit.start_posn.inpacket, nal->data_len, nal->nal_ref_idc,nal->nal_unit_type, NAL_UNIT_TYPE_STR(nal->nal_unit_type)); fprint_msg(" first_mb_in_slice %u, slice_type %u (%s)," " pic_parameter_set_id %d\n",data->first_mb_in_slice, data->slice_type,NAL_SLICE_TYPE_STR(data->slice_type), data->pic_parameter_set_id); } // If we don't have sequence/parameter sets, then we can't go any // further. Assume the caller knew what they were doing... if (seq_param_dict == NULL || pic_param_dict == NULL) return 0; // To read the frame number we need to know how long it is, which is // determined by the value of log2_max_frame_num, which is defined in the // relevant sequence parameter set, which is determined by the picture // parameter set whose id we just read. err = get_pic_param_data(pic_param_dict,data->pic_parameter_set_id, &pic_param_data); if (err) return 1; err = get_seq_param_data(seq_param_dict,pic_param_data->seq_parameter_set_id, &seq_param_data); if (err) return 1; // Whilst we've got the sequence parameter set to hand, it's convenient // to remember the pic_order_cnt_type locally, so that we don't need to // look it up again when trying to decide if this is the first VCL NAL data->seq_param_set_pic_order_cnt_type = seq_param_data->pic_order_cnt_type; if (show_nal_details) fprint_msg(" seq_param_set->pic_order_cnt_type %u\n", data->seq_param_set_pic_order_cnt_type); err = read_bits(bd,seq_param_data->log2_max_frame_num,&data->frame_num); CHECK("frame_num"); if (show_nal_details) fprint_msg(" frame_num %u (%d bits)\n",data->frame_num, seq_param_data->log2_max_frame_num); data->field_pic_flag = 0; // value if not present - i.e., a frame data->bottom_field_flag = 0; data->bottom_field_flag_present = FALSE; if (!seq_param_data->frame_mbs_only_flag) { err = read_bit(bd,&data->field_pic_flag); CHECK("field_pic_flag"); if (show_nal_details) fprint_msg(" field_pic_flag %d\n",data->field_pic_flag); if (data->field_pic_flag) { data->bottom_field_flag_present = TRUE; err = read_bit(bd,&data->bottom_field_flag); CHECK("bottom_field_flag"); if (show_nal_details) fprint_msg(" bottom_field_flag %d\n",data->bottom_field_flag); } } if (nal->nal_unit_type == 5) { err = read_exp_golomb(bd,&data->idr_pic_id); CHECK("idr_pic_id"); if (show_nal_details) fprint_msg(" idr_pic_id %u\n",data->idr_pic_id); } data->delta_pic_order_cnt_bottom = 0; // value if not present data->delta_pic_order_cnt[0] = 0; data->delta_pic_order_cnt[1] = 0; if (seq_param_data->pic_order_cnt_type == 0) { err = read_bits(bd,seq_param_data->log2_max_pic_order_cnt_lsb, &data->pic_order_cnt_lsb); CHECK("pic_order_cnt_lsb"); if (pic_param_data->pic_order_present_flag && !data->field_pic_flag) { err = read_signed_exp_golomb(bd,&data->delta_pic_order_cnt_bottom); CHECK("delta_pic_order_cnt_bottom"); } if (show_nal_details) fprint_msg(" pic_order_cnt_lsb %u (%d bits)\n" " delta_pic_order_cnt_bottom %d\n", data->pic_order_cnt_lsb, seq_param_data->log2_max_pic_order_cnt_lsb, data->delta_pic_order_cnt_bottom); } else if (seq_param_data->pic_order_cnt_type == 1 && !seq_param_data->delta_pic_order_always_zero_flag) { err = read_signed_exp_golomb(bd,&data->delta_pic_order_cnt[0]); CHECK("delta_pic_order_cnt[0]"); if (show_nal_details) fprint_msg(" delta_pic_order_cnt[0] %d\n",data->delta_pic_order_cnt[0]); if (pic_param_data->pic_order_present_flag && !data->field_pic_flag) { err = read_signed_exp_golomb(bd,&data->delta_pic_order_cnt[1]); CHECK("delta_pic_order_cnt[1]"); if (show_nal_details) fprint_msg(" delta_pic_order_cnt[1] %d\n",data->delta_pic_order_cnt[1]); } } // Since we're not claiming to support redundant pictures, we could // give up before reading the next value. However, if we *do* read // it, we can grumble about/ignore redundant pictures if we get them, // which seems a useful thing to be able to do. data->redundant_pic_cnt = 0; data->redundant_pic_cnt_present = FALSE; if (pic_param_data->redundant_pic_cnt_present_flag) { data->redundant_pic_cnt_present = TRUE; err = read_exp_golomb(bd,&data->redundant_pic_cnt); CHECK("redundant_pic_cnt"); if (show_nal_details) fprint_msg(" redundant_pic_cnt %u\n",data->redundant_pic_cnt); } nal->decoded = TRUE; return 0; } /* * Look at the start of the picture parameter set. * * Assumes that this *is* a NAL unit representing a picture parameter set. * * Don't call this directly - call read_rbsp_data() instead. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int read_pic_param_set_data(nal_unit_p nal, int show_nal_details) { int err; bitdata_p bd = nal->bit_data; nal_pic_param_data_p data = &(nal->u.pic); uint32_t temp; // Values that don't get saved into our NAL unit uint32_t num_ref_idx_10_active; uint32_t num_ref_idx_11_active; byte weighted_pred_flag; uint32_t weighted_bipred_idc; int32_t pic_init_qp; int32_t pic_init_qs; int32_t chroma_qp_index_offset; byte deblocking_filter_control_present_flag; byte constrained_intra_pred_flag; #undef CHECK #define CHECK(name) \ if (err) \ { \ fprint_err("### Error reading %s field from picture parameter set\n",\ (name)); \ } // We need to know the id of this picture parameter set, and also // which sequence parameter set it refers to err = read_exp_golomb(bd,&temp); CHECK("pic_parameter_set_id"); data->pic_parameter_set_id = temp; err = read_exp_golomb(bd,&temp); CHECK("seq_parameter_set_id"); data->seq_parameter_set_id = temp; err = read_bit(bd,&data->entropy_coding_mode_flag); CHECK("entropy_coding_mode_flag"); // We care about pic_order_present_flag err = read_bit(bd,&data->pic_order_present_flag); CHECK("pic_order_present_flag"); if (show_nal_details) { fprint_msg("@@ PPS " OFFSET_T_FORMAT_08 "/%04d: size %d\n" " nal_ref_idc %x nal_unit_type %02x (%s)\n", nal->unit.start_posn.infile,nal->unit.start_posn.inpacket, nal->data_len, nal->nal_ref_idc,nal->nal_unit_type, NAL_UNIT_TYPE_STR(nal->nal_unit_type)); fprint_msg(" pic_parameter_set_id %d, seq_parameter_set_id %d\n", data->pic_parameter_set_id,data->seq_parameter_set_id); fprint_msg(" entropy_coding_mode_flag %d\n", data->entropy_coding_mode_flag); fprint_msg(" pic_order_present_flag %d\n",data->pic_order_present_flag); } // After this, we don't (at the moment) really need any of the rest. // However, it is moderately useful (for paranoia's sake) to read the // redundant_pic_cnt_present_flag at the very end, and we should not // be interpreting the inside of a picture parameter set very often... err = read_exp_golomb(bd,&data->num_slice_groups); // minus 1 CHECK("num_slice_groups"); data->num_slice_groups ++; if (show_nal_details) fprint_msg(" num_slice_groups %u\n",data->num_slice_groups); if (data->num_slice_groups > 1) { err = read_exp_golomb(bd,&data->slice_group_map_type); CHECK("slice_group_map_type"); if (show_nal_details) fprint_msg(" slice_group_map_type %u\n",data->slice_group_map_type); if (data->slice_group_map_type == 0) { // NB: 0..num_slice_groups-1, not 0..num_slice_groups-1 - 1 unsigned int igroup; for (igroup=0; igroup < data->num_slice_groups; igroup++) { uint32_t ignint; err = read_exp_golomb(bd,&ignint); // run_length_minus1[igroup] CHECK("run_length_minus1[x]"); } } else if (data->slice_group_map_type == 2) { // But this time, 0..num_slice_groups-1 - 1 unsigned int igroup; for (igroup=0; igroup < (data->num_slice_groups - 1); igroup++) { uint32_t ignint; err = read_exp_golomb(bd,&ignint); // top_left[igroup] CHECK("top_left[x]"); err = read_exp_golomb(bd,&ignint); // bottom_right[igroup] CHECK("bottom_right[x]"); } } else if (data->slice_group_map_type == 3 || data->slice_group_map_type == 4 || data->slice_group_map_type == 5) { byte ignbyte; uint32_t ignint; err = read_bit(bd,&ignbyte); // slice_group_change_direction_flag CHECK("slice_group_change_direction_flag"); err = read_exp_golomb(bd,&ignint); // slice_group_change_rate_minus1 CHECK("slice_group_change_rate_minus1"); } else if (data->slice_group_map_type == 6) { uint32_t pic_size_in_map_units; unsigned int ii; int size; err = read_exp_golomb(bd,&pic_size_in_map_units); // minus 1 pic_size_in_map_units ++; CHECK("pic_size_in_map_units"); if (show_nal_details) fprint_msg(" pic_size_in_map_units %u\n",pic_size_in_map_units); size = (int) ceil(log2(data->num_slice_groups)); // Again, notice the range for (ii=0; ii < pic_size_in_map_units; ii++) { uint32_t ignint; err = read_bits(bd,size,&ignint); // slice_group_id[ii] CHECK("slice_group_id[x]"); } } } err = read_exp_golomb(bd,&num_ref_idx_10_active); // minus 1 CHECK("num_ref_idx_10_active"); num_ref_idx_10_active ++; if (show_nal_details) fprint_msg(" num_ref_idx_10_active %u\n",num_ref_idx_10_active); err = read_exp_golomb(bd,&num_ref_idx_11_active); // minus 1 CHECK("num_ref_idx_11_active"); num_ref_idx_11_active ++; if (show_nal_details) fprint_msg(" num_ref_idx_11_active %u\n",num_ref_idx_11_active); err = read_bit(bd,&weighted_pred_flag); CHECK("weighted_pred_flag"); if (show_nal_details) fprint_msg(" weighted_pred_flag %d\n",weighted_pred_flag); err = read_bits(bd,2,&weighted_bipred_idc); CHECK("weighted_bipred_idc"); if (show_nal_details) fprint_msg(" weighted_bipred_idc %u\n",weighted_bipred_idc); err = read_signed_exp_golomb(bd,&pic_init_qp); // minus 26 CHECK("pic_init_qp"); pic_init_qp += 26; if (show_nal_details) fprint_msg(" pic_init_qp %d\n",pic_init_qp); err = read_signed_exp_golomb(bd,&pic_init_qs); // minus 26 CHECK("pic_init_qs"); pic_init_qs += 26; if (show_nal_details) fprint_msg(" pic_init_qs %d\n",pic_init_qs); err = read_signed_exp_golomb(bd,&chroma_qp_index_offset); CHECK("chroma_qp_index_offset"); if (show_nal_details) fprint_msg(" chroma_qp_index_offset %d\n",chroma_qp_index_offset); err = read_bit(bd,&deblocking_filter_control_present_flag); CHECK("deblocking_filter_control_present_flag"); if (show_nal_details) fprint_msg(" deblocking_filter_control_present_flag %d\n", deblocking_filter_control_present_flag); err = read_bit(bd,&constrained_intra_pred_flag); CHECK("constrained_intra_pred_flag"); if (show_nal_details) fprint_msg(" constrained_intra_pred_flag %d\n",constrained_intra_pred_flag); // We (sort of) care about redundant_pic_cnt_present_flag // in that we need to know it if we are going to read the later bits // of a slice header err = read_bit(bd,&data->redundant_pic_cnt_present_flag); CHECK("redundant_pic_cnt_present_flag"); if (show_nal_details) fprint_msg(" redundant_pic_cnt_present_flag %d\n", data->redundant_pic_cnt_present_flag); nal->decoded = TRUE; return 0; } /* * Look at the start of the sequence parameter set. * * Assumes that this *is* a NAL unit representing a sequence parameter set. * * Don't call this directly - call read_rbsp_data() instead. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int read_seq_param_set_data(nal_unit_p nal, int show_nal_details) { int err; bitdata_p bd = nal->bit_data; nal_seq_param_data_p data = &(nal->u.seq); uint32_t temp; // Values that don't get saved into our NAL unit byte reserved_zero_5bits; uint32_t num_ref_frames; byte gaps_in_frame_num_value_allowed_flag; uint32_t pic_width_in_mbs; uint32_t pic_height_in_map_units; #undef CHECK #define CHECK(name) \ if (err) \ { \ fprint_err("### Error reading %s field from sequence parameter set\n",\ (name)); \ } err = read_bits_into_byte(bd,8,&data->profile_idc); CHECK("profile_idc"); err = read_bit(bd,&data->constraint_set0_flag); CHECK("constraint_set0_flag"); err = read_bit(bd,&data->constraint_set1_flag); CHECK("constraint_set1_flag"); err = read_bit(bd,&data->constraint_set2_flag); CHECK("constraint_set2_flag"); err = read_bits_into_byte(bd,5,&reserved_zero_5bits); CHECK("reserved_zero_5bits"); if (reserved_zero_5bits != 0) { fprint_err("### reserved_zero_5bits not zero (%d) in sequence" " parameter set NAL unit at " OFFSET_T_FORMAT "/%d\n", reserved_zero_5bits, nal->unit.start_posn.infile,nal->unit.start_posn.inpacket); print_data(FALSE," Data",nal->bit_data->data,nal->bit_data->data_len, 20); // Should we carry on or give up? On the whole, if this is broken // we can't really trust the rest of its data... return 1; } err = read_bits_into_byte(bd,8,&data->level_idc); CHECK("level_idc"); if (show_nal_details) { fprint_msg("@@ SPS " OFFSET_T_FORMAT_08 "/%04d: size %d\n" " nal_ref_idc %x nal_unit_type %02x (%s)\n", nal->unit.start_posn.infile,nal->unit.start_posn.inpacket, nal->data_len, nal->nal_ref_idc,nal->nal_unit_type, NAL_UNIT_TYPE_STR(nal->nal_unit_type)); fprint_msg(" profile_idc %u, constraint set flags: %d %d %d\n", data->profile_idc, data->constraint_set0_flag, data->constraint_set1_flag, data->constraint_set2_flag); fprint_msg(" level_idc %u\n",data->level_idc); } err = read_exp_golomb(bd,&temp); CHECK("seq_parameter_set_id"); data->seq_parameter_set_id = temp; // We care about log2_max_frame_num_minus4 err = read_exp_golomb(bd,&data->log2_max_frame_num); // minus 4 CHECK("log2_max_frame_num"); data->log2_max_frame_num += 4; // We care about pic_order_cnt_type err = read_exp_golomb(bd,&data->pic_order_cnt_type); CHECK("pic_order_cnt_type"); if (show_nal_details) { fprint_msg(" seq_parameter_set_id %u\n",data->seq_parameter_set_id); fprint_msg(" log2_max_frame_num %u\n",data->log2_max_frame_num); fprint_msg(" pic_order_cnt_type %u\n",data->pic_order_cnt_type); } if (data->pic_order_cnt_type == 0) { err = read_exp_golomb(bd,&data->log2_max_pic_order_cnt_lsb); // minus 4 CHECK("log2_max_pic_order_cnt_lsb"); data->log2_max_pic_order_cnt_lsb += 4; if (show_nal_details) fprint_msg(" log2_max_pic_order_cnt_lsb %u\n", data->log2_max_pic_order_cnt_lsb); } else if (data->pic_order_cnt_type == 1) { unsigned int ii; int32_t offset_for_non_ref_pic; int32_t offset_for_top_to_bottom_field; uint32_t num_ref_frames_in_pic_order_cnt_cycle; err = read_bit(bd,&data->delta_pic_order_always_zero_flag); CHECK("delta_pic_order_always_zero_flag"); if (show_nal_details) fprint_msg(" delta_pic_order_always_zero_flag %d\n", data->delta_pic_order_always_zero_flag); err = read_signed_exp_golomb(bd,&offset_for_non_ref_pic); CHECK("offset_for_non_ref_pic"); err = read_signed_exp_golomb(bd,&offset_for_top_to_bottom_field); CHECK("offset_for_top_to_bottom_field"); err = read_exp_golomb(bd,&num_ref_frames_in_pic_order_cnt_cycle); CHECK("num_ref_frames_in_pic_order_cnt_cycle"); // The standard says that num_ref_frames_in_pic_order_cnt_cycle // shall be in the range 0..255 for (ii=0; ii < num_ref_frames_in_pic_order_cnt_cycle; ii++) { int32_t offset_for_ref_frame_XX; err = read_signed_exp_golomb(bd,&offset_for_ref_frame_XX); // XX = [ii] CHECK("offset_for_ref_frame_X"); } } err = read_exp_golomb(bd,&num_ref_frames); CHECK("num_ref_frames"); if (show_nal_details) fprint_msg(" num_ref_frames %u\n",num_ref_frames); err = read_bit(bd,&gaps_in_frame_num_value_allowed_flag); CHECK("gaps_in_frame_num_value_allowed_flag"); if (show_nal_details) if (show_nal_details) fprint_msg(" gaps_in_frame_num_value_allowed_flag %d\n", gaps_in_frame_num_value_allowed_flag); err = read_exp_golomb(bd,&pic_width_in_mbs); // minus 1 CHECK("pic_width_in_mbs"); pic_width_in_mbs ++; if (show_nal_details) fprint_msg(" pic_width_in_mbs %u\n",pic_width_in_mbs); err = read_exp_golomb(bd,&pic_height_in_map_units); // minus 1 CHECK("pic_height_in_map_units"); pic_height_in_map_units ++; if (show_nal_details) fprint_msg(" pic_height_in_map_units %u\n",pic_height_in_map_units); // We care about frame_mbs_only_flag err = read_bit(bd,&data->frame_mbs_only_flag); CHECK("frame_mbs_only_flag"); if (show_nal_details) fprint_msg(" frame_mbs_only_flag %d\n",data->frame_mbs_only_flag); nal->decoded = TRUE; return 0; } /* * Read the data for an SEI recovery point * * Returns 0 if it succeeds, 1 if some error occurs */ static int read_SEI_recovery_point(nal_unit_p nal, int payloadSize, int show_nal_details) { int err; bitdata_p bd = nal->bit_data; nal_SEI_recovery_data_p data = &(nal->u.sei_recovery); uint32_t temp; #undef CHECK #define CHECK(name) \ if (err) \ { \ fprint_err("### Error reading %s field from SEI\n", \ (name)); \ } err = read_exp_golomb(bd,&temp); CHECK("recovery_frame_cnt"); data->recovery_frame_cnt = temp; err = read_bit(bd,&data->exact_match_flag); CHECK("exact_match_flag"); err = read_bit(bd,&data->broken_link_flag); CHECK("broken_link_flag"); err = read_bits(bd,2,&data->changing_slice_group_idc); CHECK("changing_slice_group_idc"); nal->decoded = TRUE; if (show_nal_details) { print_msg("@@ Recovery Point SEI\n"); fprint_msg(" recovery_frame_cnt %d\n exact_match_flag %d\n", data->recovery_frame_cnt, data->exact_match_flag); fprint_msg(" broken_link_flag %d\n changing_slice_group_idc %d", data->broken_link_flag, data->changing_slice_group_idc); } return 0; } /* * Look at the start of an SEI * * Assumes that this *is* a NAL unit representing an SEI * * Don't call this directly - call read_rbsp_data() instead. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int read_SEI(nal_unit_p nal, int show_nal_details) { int err; int SEI_payloadType = 0; int SEI_payloadSize = 0; // in byte bitdata_p bd = nal->bit_data; uint32_t temp = 0; #undef CHECK #define CHECK(name) \ if (err) \ { \ fprint_err("### Error reading %s field from SEI\n", \ (name)); \ } // read payloadtype (see H.264:7.3.2.3.1) for (;;) { err = read_bits(bd,8,&temp); CHECK("payloadType"); if (temp == 0xff) SEI_payloadType += 0xff; else break; } SEI_payloadType += temp; nal->u.sei_recovery.payloadType = SEI_payloadType; // read payloadSize for (;;) { err = read_bits(bd,8,&temp); CHECK("payloadSize"); if (temp == 0xff) SEI_payloadSize += 0xff; else break; } SEI_payloadSize += temp; nal->u.sei_recovery.payloadSize = SEI_payloadSize; if (SEI_payloadType == 6) // SEI recovery_point err = read_SEI_recovery_point(nal, SEI_payloadSize, show_nal_details); return 0; } /* * Look at the start of the RBSP for a NAL unit * * Decodes some or all of the data for slices, sequence parameter sets * and picture parameter sets. * * (Note that calling this more than once does not read the data * more than once. Also note that the RBSP and bitdata datastructures * in the NAL unit do not persist after this call.) * * Caveat: if either `seq_param_dict` or `pic_param_dict` is NULL, and the * NAL unit being interpreted is an IDR or non-IDR unit (i.e., a slice), * then only the first few values in the RBSP will be read (up to and * including the slice_type and pic_parameter_set_id), and the RBSP will * *not* be marked as decoded. Because of this, calling this * function again later will cause the RBSP to be read again (strictly, * to be re-extracted and read again). * * Returns 0 if it succeeds, 1 if some error occurs. */ static int read_rbsp_data(nal_unit_p nal, param_dict_p seq_param_dict, param_dict_p pic_param_dict, int show_nal_details) { int err = 0; if (nal->decoded) return 0; err = prepare_rbsp(nal); if (err) return err; if (nal->nal_unit_type == 1 || nal->nal_unit_type == 5) // Coded slice of a (non) IDR picture err = read_slice_data(nal,seq_param_dict,pic_param_dict,show_nal_details); else if (nal->nal_unit_type == 8) // Picture parameter set err = read_pic_param_set_data(nal,show_nal_details); else if (nal->nal_unit_type == 7) // Sequence parameter set err = read_seq_param_set_data(nal,show_nal_details); else if (nal->nal_unit_type == 6) // SEI err = read_SEI(nal,show_nal_details); else if (show_nal_details) fprint_msg("@@ nal " OFFSET_T_FORMAT_08 "/%04d: size %d\n" " nal_ref_idc %x nal_unit_type %02x (%s)\n", nal->unit.start_posn.infile,nal->unit.start_posn.inpacket, nal->data_len, nal->nal_ref_idc,nal->nal_unit_type, NAL_UNIT_TYPE_STR(nal->nal_unit_type)); if (err) { fprint_err("### Error reading RBSP data for %s NAL (ref idc %x," " unit type %x) at " OFFSET_T_FORMAT_08 "/%04d\n", NAL_UNIT_TYPE_STR(nal->nal_unit_type), nal->nal_ref_idc, nal->nal_unit_type, nal->unit.start_posn.infile, nal->unit.start_posn.inpacket); } // At this point, we've finished with the actual RBSP data // so we might as well free it and save some space. if (nal->rbsp != NULL) { free(nal->rbsp); nal->rbsp = NULL; nal->rbsp_len = 0; free_bitdata(&nal->bit_data); } return err; } /* * Is this NAL unit a slice? * * Returns true if its ``nal_unit_type`` is 1 (coded slice of IDR picture) * or 5 (coded slice of IDR picture). */ extern int nal_is_slice(nal_unit_p nal) { return nal->nal_unit_type == 1 || nal->nal_unit_type == 5; } /* * Is this NAL unit a picture parameter set? * * Returns true if its ``nal_unit_type`` is 8. */ extern int nal_is_pic_param_set(nal_unit_p nal) { return nal->nal_unit_type == 8; } /* * Is this NAL unit a sequence parameter set? * * Returns true if its ``nal_unit_type`` is 7. */ extern int nal_is_seq_param_set(nal_unit_p nal) { return nal->nal_unit_type == 7; } /* * Is this NAL unit marked as part of a redundant picture? */ extern int nal_is_redundant(nal_unit_p nal) { return nal_is_slice(nal) && nal->u.slice.redundant_pic_cnt_present && nal->u.slice.redundant_pic_cnt; } /* * Is this VCL NAL unit the first of a new primary coded picture? * * - `nal` is the NAL unit we need to decide about. * - `last` is a slice NAL unit from the last primary coded picture * (likely to be the first NAL unit therefrom, in fact) * * Both `nal` and `last` must be VCL NALs representing slices of a reference * picture - i.e., with nal_unit_type 1 or 5 (if we were supporting type A * slice data partitions, we would have to take them into account as well). * * Both `nal` and `last` must have had their innards decoded with * `read_slice_data`, which should have occurred automatically if they are * both appropriate NAL units for this process. * * Acording to H.264 7.4.1.2.4 (from the JVT-J010d7 draft): * * The first NAL unit of a new primary code picture can be detected * because: * * - its frame number differs in value from that of the last slice (NB: * IDR pictures always have frame_num == 0) * * - its field_pic_flag differs in value (i.e., one is a field slice, and * the other a frame slice) * * - the bottom_field_flag is present in both (determined by * frame_mbs_only_flag in the sequence parameter set, and by * field_pic_flag) and differs (i.e., both are field slices, but one * is top and the other bottom) [*]_ * * - nal_ref_idc differs in value, and one of them has nal_ref_idc == 0 * (i.e., one is a reference picture and the other is not) * * - pic_order_cnt_type (found in the sequence parameter set) == 0 for * both and either pic_order_cnt_lsb differs in value or * delta_pic_order_cnt_bottom differs in value [*]_ * * - pic_order_cnt_type == 1 for both and either delta_pic_order_cnt[0] * or delta_pic_order_cnt[1] differs in value [*]_ * * - nal_unit_type == 5 for one and not in the other (i.e., one is IDR * and the other is not) * * - nal_unit_type == 5 for both (i.e., both are IDR), and idr_pic_id * differs (i.e., they're not the same IDR) * * It is possible that later drafts may alter/augment these criteria - * that has already happened between JVT-G050r1 and JVT-J010d7. * * .. [*] For these three items, we need to have decoded the active * sequence parameter set (which, for now, I'll assume to be the last * set we found with the appropriate id). */ extern int nal_is_first_VCL_NAL(nal_unit_p nal, nal_unit_p last) { nal_slice_data_p this,that; if (nal->starts_picture_decided) return nal->starts_picture; if (!nal->decoded) { print_err("### Cannot decide if NAL unit is first VCL NAL\n" " its RBSP data has not been interpreted\n"); return FALSE; } // Since we intend to transmit all sequence and picture parameter // sets, at least initially, we don't need to worry if they are // "inside" a picture, at its start, or whatever. // Since we're not supporting data partition slices A,B,C, we can // ignore them as well (at least, so I hope) if (nal->nal_unit_type != NAL_NON_IDR && nal->nal_unit_type != NAL_IDR) { nal->starts_picture = FALSE; nal->starts_picture_decided = TRUE; return FALSE; } nal->starts_picture = TRUE; // let's be optimistic... nal->starts_picture_decided = TRUE; if (last == NULL) { // With nothing else to compare to, we shall assume that we do // "start" a picture nal->start_reason = "First slice in data stream"; return TRUE; } this = &(nal->u.slice); that = &(last->u.slice); if (this->frame_num != that->frame_num) nal->start_reason = "Frame number differs"; else if (this->field_pic_flag != that->field_pic_flag) nal->start_reason = "One is field, the other frame"; else if (this->bottom_field_flag_present && that->bottom_field_flag_present && this->bottom_field_flag != that->bottom_field_flag) { nal->start_reason = "One is bottom field, the other top"; // In which case, we'll need to remember to output the OTHER // half of the frame when we find it... } else if (nal->nal_ref_idc != last->nal_ref_idc && (nal->nal_ref_idc == 0 || last->nal_ref_idc == 0)) nal->start_reason = "One is reference picture, the other is not"; else if (this->seq_param_set_pic_order_cnt_type == 0 && that->seq_param_set_pic_order_cnt_type == 0 && (this->pic_order_cnt_lsb != that->pic_order_cnt_lsb || this->delta_pic_order_cnt_bottom != that->delta_pic_order_cnt_bottom)) nal->start_reason = "Picture order counts differ"; else if (this->seq_param_set_pic_order_cnt_type == 1 && that->seq_param_set_pic_order_cnt_type == 1 && (this->delta_pic_order_cnt[0] != that->delta_pic_order_cnt[0] || this->delta_pic_order_cnt[1] != that->delta_pic_order_cnt[1])) nal->start_reason = "Picture delta counts differ"; else if ((nal->nal_unit_type == 5 || last->nal_unit_type == 5) && nal->nal_unit_type != last->nal_unit_type) nal->start_reason = "One IDR, one not"; else if (nal->nal_unit_type == 5 && last->nal_unit_type == 5 && this->idr_pic_id != that->idr_pic_id) nal->start_reason = "Different IDRs"; else nal->starts_picture = FALSE; return nal->starts_picture; } /* * Print out useful information about this NAL unit, on the given stream. * * This is intended as a single line of information. */ extern void report_nal(int is_msg, nal_unit_p nal) { if (nal == NULL) fprint_msg_or_err(is_msg,".............: NAL unit \n"); else if (nal_is_slice(nal) && (nal->nal_unit_type == NAL_IDR || nal->nal_unit_type == NAL_NON_IDR)) { #define SARRAYSIZE 20 char what[SARRAYSIZE]; snprintf(what,SARRAYSIZE,"(%s)",NAL_UNIT_TYPE_STR(nal->nal_unit_type)); // On Windows, snprintf does not guarantee to write a terminating NULL what[SARRAYSIZE-1] = '\0'; fprint_msg_or_err(is_msg,OFFSET_T_FORMAT_08 "/%04d: %x/%02x %-20s %u (%s) frame %u", nal->unit.start_posn.infile, nal->unit.start_posn.inpacket, nal->nal_ref_idc, nal->nal_unit_type, what, nal->u.slice.slice_type, NAL_SLICE_TYPE_STR(nal->u.slice.slice_type), nal->u.slice.frame_num); if (nal->u.slice.field_pic_flag) { if (nal->u.slice.bottom_field_flag) fprint_msg_or_err(is_msg," [bottom]"); else fprint_msg_or_err(is_msg," [top]"); } } else if (nal_is_seq_param_set(nal)) { fprint_msg_or_err(is_msg,OFFSET_T_FORMAT_08 "/%04d: %x/%02x (%s %u)", nal->unit.start_posn.infile, nal->unit.start_posn.inpacket, nal->nal_ref_idc, nal->nal_unit_type, NAL_UNIT_TYPE_STR(nal->nal_unit_type), nal->u.seq.seq_parameter_set_id); } else if (nal_is_pic_param_set(nal)) { fprint_msg_or_err(is_msg,OFFSET_T_FORMAT_08 "/%04d: %x/%02x (%s %u)", nal->unit.start_posn.infile, nal->unit.start_posn.inpacket, nal->nal_ref_idc, nal->nal_unit_type, NAL_UNIT_TYPE_STR(nal->nal_unit_type), nal->u.pic.pic_parameter_set_id); } else fprint_msg_or_err(is_msg,OFFSET_T_FORMAT_08 "/%04d: %x/%02x (%s)", nal->unit.start_posn.infile, nal->unit.start_posn.inpacket, nal->nal_ref_idc, nal->nal_unit_type, NAL_UNIT_TYPE_STR(nal->nal_unit_type)); #if REPORT_NAL_SHOWS_ADDRESS fprint_msg_or_err(is_msg," <%p>",nal); #endif fprint_msg_or_err(is_msg,"\n"); } // ------------------------------------------------------------ // Check profile // ------------------------------------------------------------ /* * Issue a warning if the profile of this bitstream is "unsuitable". * * "suitable" bitstream declares itself as either conforming to * the main profile, or as obeying the constraints of the main profile. * * This function should be called on the first sequence parameter set * NAL unit in the bitstream, after its innards have been decoded. */ static void check_profile(nal_unit_p nal, int show_nal_details) { struct nal_seq_param_data data; char *name; if (nal == NULL) { print_err("### Attempt to check profile on a NULL NAL unit\n"); return; } else if (nal->nal_unit_type != 7) { print_err("### Attempt to check profile on a NAL unit that is not a " "sequence parameter set\n"); report_nal(FALSE,nal); return; } else if (!nal->decoded) { // Note that we believe ourselves safe in passing NULLs for the // parameter NAL units, since we are reading a sequence parameter set, // which does not depend on anything else int err = read_rbsp_data(nal,NULL,NULL,show_nal_details); if (err) { print_err("### Error trying to decode RBSP for first sequence" " parameter set\n"); return; } } data = nal->u.seq; name = (data.profile_idc==66?"baseline": data.profile_idc==77?"main": data.profile_idc==88?"extended":""); if (data.profile_idc == 77 || data.constraint_set1_flag == 1) return; else { int sum = data.constraint_set0_flag + data.constraint_set1_flag + data.constraint_set2_flag; print_err("\n"); fprint_err("Warning: This bitstream declares itself as %s profile (%d)", name,data.profile_idc); if (sum == 0) print_err(".\n"); else { print_err(",\n"); print_err(" and as obeying the constraints of the"); if (data.constraint_set0_flag) print_err(" baseline"); if (data.constraint_set1_flag) print_err(" main"); if (data.constraint_set2_flag) print_err(" extended"); fprint_err(" profile%s.\n",(sum==1?"":"s")); } fprint_err(" This software does not support %s profile,\n", name); print_err(" and may give incorrect results or fail.\n\n"); return; } } // ------------------------------------------------------------ // NAL unit *data* stuff // ------------------------------------------------------------ /* * Once we've read the *data* for a NAL unit, we can set up a bit more * of the datastructure. * * - `verbose` is true if a brief report on the NAL unit should be given * - `nal` is the NAL unit itself. * * CAVEAT: This function is declared external so that I can use it * in `stream_type.c`, but it is not exported to the nalunit_defns.h file * because I cannot see any sensible outside use for it... * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int setup_NAL_data(int verbose, nal_unit_p nal) { int forbidden_zero_bit; // Although we've read "all" the data (i.e., including the prefix), // for most purposes of working on it it's more convenient to act // as if those bytes aren't there... nal->data = &(nal->unit.data[3]); nal->data_len = nal->unit.data_len - 3; // The first byte of our data tells us what sort of NAL unit it is forbidden_zero_bit = nal->data[0] & 0x80; if (forbidden_zero_bit) { fprint_err("### NAL forbidden_zero_bit is non-zero, at " OFFSET_T_FORMAT "/%d\n", nal->unit.start_posn.infile,nal->unit.start_posn.inpacket); fprint_err(" First byte of NAL unit is %02x",nal->data[0]); if (nal->data[0] == 0xB3) print_err(", which is H.262 sequence header start code\n" " Data may be MPEG-1 or MPEG-2"); print_err("\n"); return 1; } nal->nal_ref_idc = (nal->data[0] & 0x60) >> 5; nal->nal_unit_type = (nal->data[0] & 0x1F); if (verbose) { #define SARRAYSIZE2 20 char what[SARRAYSIZE2]; snprintf(what,SARRAYSIZE2,"(%s)",NAL_UNIT_TYPE_STR(nal->nal_unit_type)); // On Windows, snprintf does not guarantee to write a terminating NULL what[SARRAYSIZE2-1] = '\0'; fprint_msg(OFFSET_T_FORMAT_08 "/%04d: NAL unit %d/%d %-20s", nal->unit.start_posn.infile, nal->unit.start_posn.inpacket, nal->nal_ref_idc,nal->nal_unit_type,what); // Show the start of the data bytes. This is a tailored form of what // `print_data` would do, more suited to our purposes here (i.e., // wanting multiple rows of output to line up neatly in columns). if (nal->data_len > 0) { int ii; int show_len = (nal->data_len>10?10:nal->data_len); fprint_msg(" %6d:",nal->data_len); for (ii = 0; ii < show_len; ii++) fprint_msg(" %02x",nal->data[ii]); if (show_len < nal->data_len) print_msg("..."); } print_msg("\n"); } return 0; } /* * Find and read in the next NAL unit. * * - `context` is the NAL unit context we're reading from * - `verbose` is true if a brief report on the NAL unit should be given * - `nal` is the datastructure containing the NAL unit found, or NULL * if there was none. * * Returns: * * 0 if it succeeds, * * EOF if the end-of-file is read (i.e., there is no next NAL unit), * * 2 if the NAL unit data does not make sense, so it should be ignored * (specifically, if the NAL unit's RBSP data cannot be understood), * * 1 if some other error occurs. */ extern int find_next_NAL_unit(nal_unit_context_p context, int verbose, nal_unit_p *nal) { static int need_first_seq_param_set = TRUE; int err; err = build_nal_unit(nal); if (err) return 1; err = find_next_ES_unit(context->es,&(*nal)->unit); if (err) // 1 or EOF { free_nal_unit(nal); return err; } (context->count) ++; if (context->show_nal_details) print_msg("\n"); err = setup_NAL_data(verbose,*nal); if (err) { free_nal_unit(nal); return err; } // By looking at the first sequence parameter set, we can // decide whether the data matches what we claim to support. // That also serves to bootstrap the decoding of other items if (nal_is_seq_param_set(*nal)) { if (need_first_seq_param_set) { check_profile(*nal,context->show_nal_details); need_first_seq_param_set = FALSE; } } // Once we know we've got the first sequence parameter set in hand // (which we *assume* and hope is the first thing we find!), we can // decode the innards of later things. err = read_rbsp_data(*nal,context->seq_param_dict,context->pic_param_dict, context->show_nal_details); if (err) { free_nal_unit(nal); return 2; } // If this is a picture parameter set, or a sequence parameter set, // we should remember it for later on if (nal_is_pic_param_set(*nal)) { err = remember_param_data(context->pic_param_dict, (*nal)->u.pic.pic_parameter_set_id,*nal); if (err) { print_err("### Error remembering picture parameter set "); report_nal(FALSE,*nal); free_nal_unit(nal); return 1; } } else if (nal_is_seq_param_set(*nal)) { err = remember_param_data(context->seq_param_dict, (*nal)->u.seq.seq_parameter_set_id,*nal); if (err) { print_err("### Error remembering sequence parameter set "); report_nal(FALSE,*nal); free_nal_unit(nal); return 1; } } return 0; } /* * Write (copy) the current NAL unit to the ES output stream. * * - `output` is the output stream (file descriptor) to write to * - `nal` is the NAL unit to write * * Returns 0 if all went well, 1 if something went wrong. */ extern int write_NAL_unit_as_ES(FILE *output, nal_unit_p nal) { int err = write_ES_unit(output,&(nal->unit)); if (err) { print_err("### Error writing NAL unit as ES\n"); return err; } else return 0; } /* * Write (copy) the current NAL unit to the output stream, wrapped up in a * PES within TS. * * - `output` is the TS writer to write to * - `nal` is the NAL unit to write * - `video_pid` is the video PID to use * * Returns 0 if all went well, 1 if something went wrong. */ extern int write_NAL_unit_as_TS(TS_writer_p tswriter, nal_unit_p nal, uint32_t video_pid) { // Note that we write out *all* of the data for this NAL unit, // i.e., including its 00 00 01 prefix. Also note that we write // out the data with its emulation prevention 03 bytes intact. int err = write_ES_as_TS_PES_packet(tswriter, nal->unit.data,nal->unit.data_len, video_pid,DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error writing NAL unit as TS\n"); return err; } else return 0; } // ------------------------------------------------------------ // Picture and sequence parameter set support // ------------------------------------------------------------ /* * Create a new "dictionary" for remembering picture or sequence * parameter sets. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_param_dict(param_dict_p *param_dict) { param_dict_p new = malloc(SIZEOF_PARAM_DICT); if (new == NULL) { print_err("### Unable to allocate parameter 'dictionary' datastructure\n"); return 1; } new->last_id = -1; new->last_index = -1; new->ids = malloc(sizeof(uint32_t)*NAL_PIC_PARAM_START_SIZE); if (new->ids == NULL) { print_err("### Unable to allocate array within 'dictionary'" " datastructure\n"); free(new); return 1; } new->params = malloc(SIZEOF_NAL_INNARDS*NAL_PIC_PARAM_START_SIZE); if (new->params == NULL) { print_err("### Unable to allocate array within 'dictionary'" " datastructure\n"); free(new->ids); free(new); return 1; } new->posns = malloc(SIZEOF_ES_OFFSET*NAL_PIC_PARAM_START_SIZE); if (new->posns == NULL) { print_err("### Unable to allocate array within 'dictionary'" " datastructure\n"); free(new->params); free(new->ids); free(new); return 1; } new->data_lens = malloc(sizeof(uint32_t)*NAL_PIC_PARAM_START_SIZE); if (new->data_lens == NULL) { print_err("### Unable to allocate array within 'dictionary'" " datastructure\n"); free(new->params); free(new->ids); free(new); return 1; } new->size = NAL_PIC_PARAM_START_SIZE; new->length = 0; *param_dict = new; return 0; } /* * Tidy up and free a parameters "dictionary" datastructure after we've * finished with it. * * Empties the datastructure, frees it, and sets `param_dict` to NULL. * * Does nothing if `param_dict` is already NULL. */ extern void free_param_dict(param_dict_p *param_dict) { if (*param_dict == NULL) return; free((*param_dict)->ids); free((*param_dict)->params); free((*param_dict)->posns); free((*param_dict)->data_lens); (*param_dict)->ids = NULL; (*param_dict)->params = NULL; (*param_dict)->posns = NULL; (*param_dict)->data_lens = NULL; free(*param_dict); *param_dict = NULL; } /* * Remember parameter set data in a "dictionary". * * - `param_dict` should be an appropriate "dictionary" - i.e., one * being used to store picture or sequence parameter set data, as * appropriate. * - `param_id` is the id for this picture or sequence parameter set. * - `nal` is the NAL unit containing the parameter set data. * Note that a copy will be taken of the parameter set data, which * means that the caller may free the NAL unit. * * Any previous data for this picture or sequence parameter set id will be * forgotten (overwritten). * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remember_param_data(param_dict_p param_dict, uint32_t param_id, nal_unit_p nal) { int ii; if (param_id == param_dict->last_id) { param_dict->params[param_dict->last_index] = nal->u; return 0; } for (ii=0; iilength; ii++) { if (param_dict->ids[ii] == param_id) { param_dict->params[ii] = nal->u; param_dict->posns[ii] = nal->unit.start_posn; param_dict->data_lens[ii] = nal->unit.data_len; param_dict->last_id = param_id; param_dict->last_index = ii; return 0; } } if (param_dict->length == param_dict->size) { int newsize = param_dict->size + NAL_PIC_PARAM_INCREMENT; param_dict->ids = realloc(param_dict->ids,newsize*sizeof(uint32_t)); if (param_dict->ids == NULL) { print_err("### Unable to extend parameter set dictionary array\n"); return 1; } param_dict->params = realloc(param_dict->params, newsize*SIZEOF_NAL_INNARDS); if (param_dict->params == NULL) { print_err("### Unable to extend parameter set dictionary array\n"); return 1; } param_dict->posns = realloc(param_dict->params,newsize*SIZEOF_ES_OFFSET); if (param_dict->posns == NULL) { print_err("### Unable to extend parameter set dictionary array\n"); return 1; } param_dict->data_lens = realloc(param_dict->params, newsize*sizeof(uint32_t)); if (param_dict->data_lens == NULL) { print_err("### Unable to extend parameter set dictionary array\n"); return 1; } param_dict->size = newsize; } param_dict->ids[param_dict->length] = param_id; param_dict->params[param_dict->length] = nal->u; param_dict->posns[param_dict->length] = nal->unit.start_posn; param_dict->data_lens[param_dict->length] = nal->unit.data_len; param_dict->last_id = param_id; param_dict->last_index = param_dict->length; param_dict->length++; return 0; } /* * Look up a parameter set id in the "dictionary". * * - `param_dict` is a parameter "dictionary" of the appropriate type. * - `param_id` is the id to look up. * - `param_data` is the data for that id. Do not free this, it refers * into the "dictionary" datastructure. * * Note that calls of `remember_param_data()` (i.e., altering the * "dictionary") may cause the underlying datastructures to be realloc'ed, * which in turn means that the address returned as `param_data` may not be * valid after such a call. * * Returns 0 if it succeeds, 1 if the id is not present. */ static inline int lookup_param_data(param_dict_p param_dict, uint32_t param_id, nal_innards_p *param_data) { int ii; for (ii=0; iilength; ii++) { if (param_dict->ids[ii] == param_id) { *param_data = ¶m_dict->params[ii]; param_dict->last_id = param_id; param_dict->last_index = ii; return 0; } } return 1; } /* * Retrieve the picture parameter set data for the given id. * * - `pic_param_dict` is a parameter "dictionary" of the appropriate type. * - `pic_param_id` is the id to look up. * - `pic_param_data` is the data for that id. Do not free this, it refers * into the "dictionary" datastructure. * * Note that altering the "dictionary" (with `remember_param_data()`) may * cause the underlying datastructures to be realloc'ed, which in turn means * that the address returned as `pic_param_data` may not be valid after such * an action. * * Returns 0 if it succeeds, 1 if the id is not recognised. */ extern int get_pic_param_data(param_dict_p pic_param_dict, uint32_t pic_param_id, nal_pic_param_data_p *pic_param_data) { nal_innards_p innards; int absent = lookup_param_data(pic_param_dict,pic_param_id,&innards); if (absent) { fprint_err("### Unable to find picture parameter set with id %u\n", pic_param_id); return 1; } *pic_param_data = &(innards->pic); return 0; } /* * Retrieve the sequence parameter set data for the given id. * * - `seq_param_dict` is a parameter "dictionary" of the appropriate type. * - `seq_param_id` is the id to look up. * - `seq_param_data` is the data for that id. Do not free this, it refers * into the "dictionary" datastructure. * * Note that altering the "dictionary" (with `remember_param_data()`) may * cause the underlying datastructures to be realloc'ed, which in turn means * that the address returned as `seq_param_data` may not be valid after such * an action. * * Returns 0 if it succeeds, 1 if the id is not recognised. */ extern int get_seq_param_data(param_dict_p seq_param_dict, uint32_t seq_param_id, nal_seq_param_data_p *seq_param_data) { nal_innards_p innards; int absent = lookup_param_data(seq_param_dict,seq_param_id,&innards); if (absent) { fprint_err("### Unable to find sequence parameter set with id %u\n", seq_param_id); return 1; } *seq_param_data = &(innards->seq); return 0; } // ------------------------------------------------------------ // Lists of NAL units // // This duplicates the functionality provided by ES unit lists // in es.c/h, but it works at the higher level of NAL units, // which is useful if one wants to report on the content of the // lists *as* NAL units. // ------------------------------------------------------------ /* * Build a new list-of-nal-units datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_nal_unit_list(nal_unit_list_p *list) { nal_unit_list_p new = malloc(SIZEOF_NAL_UNIT_LIST); if (new == NULL) { print_err("### Unable to allocate NAL unit list datastructure\n"); return 1; } new->length = 0; new->size = NAL_UNIT_LIST_START_SIZE; new->array = malloc(sizeof(nal_unit_p)*NAL_UNIT_LIST_START_SIZE); if (new->array == NULL) { free(new); print_err("### Unable to allocate array in NAL unit list datastructure\n"); return 1; } *list = new; return 0; } /* * Add a NAL unit to the end of the NAL unit list. Does not take a copy. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int append_to_nal_unit_list(nal_unit_list_p list, nal_unit_p nal) { if (list->length == list->size) { int newsize = list->size + NAL_UNIT_LIST_INCREMENT; list->array = realloc(list->array,newsize*sizeof(nal_unit_p)); if (list->array == NULL) { print_err("### Unable to extend NAL unit list array\n"); return 1; } list->size = newsize; } list->array[list->length++] = nal; return 0; } /* * Tidy up a NAL unit list datastructure after we've finished with it. * * If `deep` is true, then any NAL units in the list will be freed * as well (this will be a Bad Thing if anywhere else is using them). */ static inline void clear_nal_unit_list(nal_unit_list_p list, int deep) { if (list->array != NULL) { int ii; for (ii=0; iilength; ii++) { if (deep) { nal_unit_p nal = list->array[ii]; if (nal != NULL) { clear_nal_unit(nal); free(nal); } } list->array[ii] = NULL; } free(list->array); list->array = NULL; } list->length = 0; list->size = 0; } /* * Reset (empty) a NAL unit list. * * If `deep` is true, then any NAL units in the list will be freed * as well (this will be a Bad Thing if anywhere else is using them). */ extern void reset_nal_unit_list(nal_unit_list_p list, int deep) { if (list->array != NULL) { int ii; for (ii=0; iilength; ii++) { if (deep) { nal_unit_p nal = list->array[ii]; if (nal != NULL) { clear_nal_unit(nal); free(nal); } } list->array[ii] = NULL; } // We *could* also shrink it - as it is, it will never get smaller // than its maximum size. Is that likely to be a problem? } list->length = 0; } /* * Tidy up and free a NAL unit list datastructure after we've finished with it. * * Clears the datastructure, frees it and returns `list` as NULL. * * If `deep` is true, then any NAL units in the list will be freed * as well (this will be a Bad Thing if anywhere else is using them). * * Does nothing if `list` is already NULL. */ extern void free_nal_unit_list(nal_unit_list_p *list, int deep) { if (*list == NULL) return; clear_nal_unit_list(*list,deep); free(*list); *list = NULL; } /* * Report on a NAL unit list's contents, to the given stream. */ extern void report_nal_unit_list(int is_msg, char *prefix, nal_unit_list_p list) { if (prefix == NULL) prefix = ""; if (list->array == NULL) fprint_msg_or_err(is_msg,"%s\n",prefix); else { int ii; for (ii=0; iilength; ii++) { fprint_msg_or_err(is_msg,"%s",prefix); report_nal(is_msg,list->array[ii]); } } } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/nalunit_defns.h000066400000000000000000000224651261471605300177030ustar00rootroot00000000000000/* * Datastructures for manipulating NAL units in H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _nalunit_defns #define _nalunit_defns #include #include "compat.h" #include "es_defns.h" #include "bitdata_defns.h" // ------------------------------------------------------------ // Constants and definitions // ------------------------------------------------------------ enum NAL_UNIT_TYPE { NAL_UNSPECIFIED, NAL_NON_IDR, NAL_PARTITION_A, NAL_PARTITION_B, NAL_PARTITION_C, NAL_IDR, NAL_SEI, NAL_SEQ_PARAM_SET, NAL_PIC_PARAM_SET, NAL_ACCESS_UNIT_DELIM, NAL_END_OF_SEQ, NAL_END_OF_STREAM, NAL_FILLER }; #define NAL_UNIT_TYPE_STR(a) \ ((a)==NAL_UNSPECIFIED?"unspecified": \ (a)==NAL_NON_IDR?"non-IDR": \ (a)==NAL_PARTITION_A?"partition A": \ (a)==NAL_PARTITION_B?"partition B": \ (a)==NAL_PARTITION_C?"partition C": \ (a)==NAL_IDR?"IDR": \ (a)==NAL_SEI?"SEI": \ (a)==NAL_SEQ_PARAM_SET?"seq param set": \ (a)==NAL_PIC_PARAM_SET?"pic param set": \ (a)==NAL_ACCESS_UNIT_DELIM?"access unit delim": \ (a)==NAL_END_OF_SEQ?"end of seq": \ (a)==NAL_END_OF_STREAM?"end of stream": \ (a)==NAL_FILLER?"filler":"???") #define SLICE_P 0 #define SLICE_B 1 #define SLICE_I 2 #define SLICE_SP 3 #define SLICE_SI 4 #define ALL_SLICES_P 5 #define ALL_SLICES_B 6 #define ALL_SLICES_I 7 #define ALL_SLICES_SP 8 #define ALL_SLICES_SI 9 #define NAL_SLICE_TYPE_STR(a) \ ((a)==SLICE_P?"First P": (a)==SLICE_B?"First B": (a)==SLICE_I?"First I": \ (a)==SLICE_SP?"First SP": (a)==SLICE_SI?"First SI": \ (a)==ALL_SLICES_P?"All P": (a)==ALL_SLICES_B?"All B": \ (a)==ALL_SLICES_I?"All I": (a)==ALL_SLICES_SP?"All SP": \ (a)==ALL_SLICES_SI?"All SI":"??") // ------------------------------------------------------------ // Datastructures // ------------------------------------------------------------ // Data for a slice NAL unit struct nal_slice_data { uint32_t seq_param_set_pic_order_cnt_type; // from the seq param set uint32_t first_mb_in_slice; uint32_t slice_type; uint32_t pic_parameter_set_id; uint32_t frame_num; // From here onwards, the fields are not necessarily all present byte field_pic_flag; // frame or field? 0 if absent byte bottom_field_flag; // 0 if absent int bottom_field_flag_present; uint32_t idr_pic_id; uint32_t pic_order_cnt_lsb; int32_t delta_pic_order_cnt_bottom; int32_t delta_pic_order_cnt[2]; uint32_t redundant_pic_cnt; int redundant_pic_cnt_present; }; typedef struct nal_slice_data *nal_slice_data_p; #define SIZEOF_NAL_SLICE_DATA sizeof(struct nal_slice_data) // ------------------------------------------------------------ // Data for a sequence parameter set struct nal_seq_param_data { byte profile_idc; byte constraint_set0_flag; byte constraint_set1_flag; byte constraint_set2_flag; byte level_idc; uint32_t seq_parameter_set_id; // our own id (0..31) uint32_t log2_max_frame_num; uint32_t pic_order_cnt_type; uint32_t log2_max_pic_order_cnt_lsb; byte delta_pic_order_always_zero_flag; byte frame_mbs_only_flag; }; typedef struct nal_seq_param_data *nal_seq_param_data_p; #define SIZEOF_NAL_SEQ_PARAM_DATA sizeof(struct nal_seq_param_data) // ------------------------------------------------------------ // Data for a picture parameter set struct nal_pic_param_data { int pic_parameter_set_id; // our own id (0..255) int seq_parameter_set_id; // we use this byte entropy_coding_mode_flag; byte pic_order_present_flag; // we use this uint32_t num_slice_groups; uint32_t slice_group_map_type; // lots of ignored things byte redundant_pic_cnt_present_flag; // this is mildly interesting }; typedef struct nal_pic_param_data *nal_pic_param_data_p; #define SIZEOF_NAL_PIC_PARAM_DATA sizeof(struct nal_pic_param_data) // ------------------------------------------------------------ // Data for a Supplemental enhancement information (SEI) nal unit struct nal_SEI_recovery_data { int payloadType; // type of SEI unit int payloadSize; // in byte int recovery_frame_cnt; byte exact_match_flag; byte broken_link_flag; uint32_t changing_slice_group_idc; }; typedef struct nal_SEI_recovery_data *nal_SEI_recovery_data_p; #define SIZEOF_NAL_SEI_RECOVERY_DATA sizeof(struct nal_SEI_recovery_data) // ------------------------------------------------------------ // An individual NAL unit might hold any one of those... union nal_innards { struct nal_slice_data slice; struct nal_seq_param_data seq; struct nal_pic_param_data pic; struct nal_SEI_recovery_data sei_recovery; }; typedef union nal_innards *nal_innards_p; #define SIZEOF_NAL_INNARDS sizeof(union nal_innards) // ------------------------------------------------------------ // "Dictionaries" for finding a specific picture parameter set or // sequence parameter set // Picture parameter set ids are in the range 0..255 // Sequence parameter set ids are in the range 0..31 struct param_dict { int last_id; // The id of the last parameter set we wanted int last_index; // and its index in the arrays int *ids; // The ids for... union nal_innards *params; // ...the data ES_offset *posns; // Where each was read from... uint32_t *data_lens; // ...and its size int size, length; // of the arrays and their content }; typedef struct param_dict *param_dict_p; #define SIZEOF_PARAM_DICT sizeof(struct param_dict) #define NAL_PIC_PARAM_START_SIZE 20 #define NAL_PIC_PARAM_INCREMENT 20 // ------------------------------------------------------------ // A single NAL unit struct nal_unit { struct ES_unit unit; // The actual data // For most purposes, it's simplest to think about the NAL unit's data as // being the data *after* the start code prefix. Indeed, that is the way // that it is presented in the standard. Thus we provide aliases here, which // do so // (NB: since these point "into" the .unit structure, they will only be // meaningful after we've finished reading in a NAL unit's data - before // then they're undefined) byte *data; // The current NAL unit's data, excluding 00 00 01 int data_len; // And its length/size // And for some processing, we need to work with the data after // it has had its emulation 3 bytes removed byte *rbsp; // The data with 00 00 03 bytes "fixed" int rbsp_len; bitdata_p bit_data; // And a view of that as bits // Information obtained by inspection of the NAL units content int nal_ref_idc; enum NAL_UNIT_TYPE nal_unit_type; int starts_picture_decided; int starts_picture; char *start_reason; // If it starts a picture, why int decoded; // Have we "read" the innards of the NAL unit? union nal_innards u; // Admittedly an unimaginative name, but short }; typedef struct nal_unit *nal_unit_p; #define SIZEOF_NAL_UNIT sizeof(struct nal_unit) // ------------------------------------------------------------ // An expandable list of NAL units struct nal_unit_list { nal_unit_p *array; // The current array of nal units */ int length; // How many there are int size; // How big the array is }; typedef struct nal_unit_list *nal_unit_list_p; #define SIZEOF_NAL_UNIT_LIST sizeof(struct nal_unit_list) #define NAL_UNIT_LIST_START_SIZE 20 #define NAL_UNIT_LIST_INCREMENT 20 // ------------------------------------------------------------ // A context for reading NAL units from an Elementary Stream struct nal_unit_context { ES_p es; int count; // Sequence and picture parameter set "dictionaries" param_dict_p seq_param_dict; param_dict_p pic_param_dict; // Show details of each NAL units content as it is read? int show_nal_details; }; typedef struct nal_unit_context *nal_unit_context_p; #define SIZEOF_NAL_UNIT_CONTEXT sizeof(struct nal_unit_context) #endif // _nalunit_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/nalunit_fns.h000066400000000000000000000275711261471605300173750ustar00rootroot00000000000000/* * Prototypes for manipulating NAL units in H.264 elementary streams. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _nalunit_fns #define _nalunit_fns #include "nalunit_defns.h" /* * Request details of the NAL unit contents as they are read */ extern void set_show_nal_reading_details(nal_unit_context_p context, int show); /* * Build a new NAL unit context, for reading NAL units from an ES. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_nal_unit_context(ES_p es, nal_unit_context_p *context); /* * Free a NAL unit context datastructure. * * Clears the datastructure, frees it, and returns `context` as NULL. * * Does nothing if `context` is already NULL. */ extern void free_nal_unit_context(nal_unit_context_p *context); /* * Rewind a file being read as NAL units. * * A thin jacket for `seek_ES`. * * Doesn't unset the sequence and picture parameter dictionaries * that have been built up when reading the file - this may possibly * not be the desired behaviour, but should be OK for well behaved files. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int rewind_nal_unit_context(nal_unit_context_p context); /* * Build a new NAL unit datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_nal_unit(nal_unit_p *nal); /* * Tidy up and free a NAL unit datastructure after we've finished with it. * * Empties the NAL unit datastructure, frees it, and sets `nal` to NULL. * * If `nal` is already NULL, does nothing. */ extern void free_nal_unit(nal_unit_p *nal); /* * Find and read in the next NAL unit. * * - `context` is the NAL unit context we're reading from * - `verbose` is true if a brief report on the NAL unit should be given * - `nal` is the datastructure containing the NAL unit found, or NULL * if there was none. * * Returns: * * 0 if it succeeds, * * EOF if the end-of-file is read (i.e., there is no next NAL unit), * * 2 if the NAL unit data does not make sense, so it should be ignored * (specifically, if the NAL unit's RBSP data cannot be understood), * * 1 if some other error occurs. */ extern int find_next_NAL_unit(nal_unit_context_p context, int verbose, nal_unit_p *nal); /* * Write (copy) the current NAL unit to the ES output stream. * * - `output` is the output stream (file descriptor) to write to * - `nal` is the NAL unit to write * * Returns 0 if all went well, 1 if something went wrong. */ extern int write_NAL_unit_as_ES(FILE *output, nal_unit_p nal); /* * Write (copy) the current NAL unit to the output stream, wrapped up in a * PES within TS. * * - `output` is the TS writer to write to * - `nal` is the NAL unit to write * - `video_pid` is the video PID to use * * Returns 0 if all went well, 1 if something went wrong. */ extern int write_NAL_unit_as_TS(TS_writer_p tswriter, nal_unit_p nal, uint32_t video_pid); /* * Create a new "dictionary" for remembering picture or sequence * parameter sets. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_param_dict(param_dict_p *param_dict); /* * Tidy up and free a parameters "dictionary" datastructure after we've * finished with it. * * Empties the datastructure, frees it, and sets `param_dict` to NULL. * * Does nothing if `param_dict` is already NULL. */ extern void free_param_dict(param_dict_p *param_dict); /* * Remember parameter set data in a "dictionary". * * - `param_dict` should be an appropriate "dictionary" - i.e., one * being used to store picture or sequence parameter set data, as * appropriate. * - `param_id` is the id for this picture or sequence parameter set. * - `nal` is the NAL unit containing the parameter set data. * Note that a copy will be taken of the parameter set data, which * means that the caller may free the NAL unit. * * Any previous data for this picture or sequence parameter set id will be * forgotten (overwritten). * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remember_param_data(param_dict_p param_dict, uint32_t param_id, nal_unit_p nal); /* * Retrieve the picture parameter set data for the given id. * * - `pic_param_dict` is a parameter "dictionary" of the appropriate type. * - `pic_param_id` is the id to look up. * - `pic_param_data` is the data for that id. Do not free this, it refers * into the "dictionary" datastructure. * * Note that altering the "dictionary" (with `remember_param_data()`) may * cause the underlying datastructures to be realloc'ed, which in turn means * that the address returned as `pic_param_data` may not be valid after such * an action. * * Returns 0 if it succeeds, 1 if the id is not recognised. */ extern int get_pic_param_data(param_dict_p pic_param_dict, uint32_t pic_param_id, nal_pic_param_data_p *pic_param_data); /* * Retrieve the sequence parameter set data for the given id. * * - `seq_param_dict` is a parameter "dictionary" of the appropriate type. * - `seq_param_id` is the id to look up. * - `seq_param_data` is the data for that id. Do not free this, it refers * into the "dictionary" datastructure. * * Note that altering the "dictionary" (with `remember_param_data()`) may * cause the underlying datastructures to be realloc'ed, which in turn means * that the address returned as `seq_param_data` may not be valid after such * an action. * * Returns 0 if it succeeds, 1 if the id is not recognised. */ extern int get_seq_param_data(param_dict_p seq_param_dict, uint32_t seq_param_id, nal_seq_param_data_p *seq_param_data); /* * Is this NAL unit a slice? * * Returns true if its ``nal_unit_type`` is 1 (coded slice of IDR picture) * or 5 (coded slice of IDR picture). */ extern int nal_is_slice(nal_unit_p nal); /* * Is this NAL unit a picture parameter set? * * Returns true if its ``nal_unit_type`` is 8. */ extern int nal_is_pic_param_set(nal_unit_p nal); /* * Is this NAL unit a sequence parameter set? * * Returns true if its ``nal_unit_type`` is 7. */ extern int nal_is_seq_param_set(nal_unit_p nal); /* * Is this NAL unit marked as part of a redundant picture? */ extern int nal_is_redundant(nal_unit_p nal); /* * Is this VCL NAL unit the first of a new primary coded picture? * * - `nal` is the NAL unit we need to decide about. * - `last` is a slice NAL unit from the last primary coded picture * (likely to be the first NAL unit therefrom, in fact) * * Both `nal` and `last` must be VCL NALs representing slices of a reference * picture - i.e., with nal_unit_type 1 or 5 (if we were supporting type A * slice data partitions, we would have to take them into account as well). * * Both `nal` and `last` must have had their innards decoded with * `read_slice_data`, which should have occurred automatically if they are * both appropriate NAL units for this process. * * Acording to H.264 7.4.1.2.4 (from the JVT-J010d7 draft): * * The first NAL unit of a new primary code picture can be detected * because: * * - its frame number differs in value from that of the last slice (NB: * IDR pictures always have frame_num == 0) * * - its field_pic_flag differs in value (i.e., one is a field slice, and * the other a frame slice) * * - the bottom_field_flag is present in both (determined by * frame_mbs_only_flag in the sequence parameter set, and by * field_pic_flag) and differs (i.e., both are field slices, but one * is top and the other bottom) [*]_ * * - nal_ref_idc differs in value, and one of them has nal_ref_idc == 0 * (i.e., one is a reference picture and the other is not) * * - pic_order_cnt_type (found in the sequence parameter set) == 0 for * both and either pic_order_cnt_lsb differs in value or * delta_pic_order_cnt_bottom differs in value [*]_ * * - pic_order_cnt_type == 1 for both and either delta_pic_order_cnt[0] * or delta_pic_order_cnt[1] differs in value [*]_ * * - nal_unit_type == 5 for one and not in the other (i.e., one is IDR * and the other is not) * * - nal_unit_type == 5 for both (i.e., both are IDR), and idr_pic_id * differs (i.e., they're not the same IDR) * * It is possible that later drafts may alter/augment these criteria - * that has already happened between JVT-G050r1 and JVT-J010d7. * * .. [*] For these three items, we need to have decoded the active * sequence parameter set (which, for now, I'll assume to be the last * set we found with the appropriate id). */ extern int nal_is_first_VCL_NAL(nal_unit_p nal, nal_unit_p last); // ------------------------------------------------------------ // Lists of NAL units // // This duplicates the functionality provided by ES unit lists // in es.h, but it works at the higher level of NAL units, // which is useful if one wants to report on the content of the // lists *as* NAL units. // ------------------------------------------------------------ /* * Build a new list-of-nal-units datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_nal_unit_list(nal_unit_list_p *list); /* * Add a NAL unit to the end of the NAL unit list. Does not take a copy. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int append_to_nal_unit_list(nal_unit_list_p list, nal_unit_p nal); /* * Reset (empty) a NAL unit list. * * If `deep` is true, then any NAL units in the list will be freed * as well (this will be a Bad Thing if anywhere else is using them). */ extern void reset_nal_unit_list(nal_unit_list_p list, int deep); /* * Tidy up and free a NAL unit list datastructure after we've finished with it. * * If `deep` is true, then any NAL units in the list will be freed * as well (this will be a Bad Thing if anywhere else is using them). * * Clears the datastructure, frees it and returns `list` as NULL. * * Does nothing if `list` is already NULL. */ extern void free_nal_unit_list(nal_unit_list_p *list, int deep); /* * Report on a NAL unit list's contents, to the given stream. */ extern void report_nal_unit_list(int is_msg, char *prefix, nal_unit_list_p list); /* * Print out useful information about this NAL unit, on the given stream. * * This is intended as a single line of information. */ extern void report_nal(int is_msg, nal_unit_p nal); #endif // _nalunit_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pcap.c000066400000000000000000000353631261471605300157710ustar00rootroot00000000000000/* pcap.c */ /* * Read pcap files. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Richard Watts, Kynesim * * ***** END LICENSE BLOCK ***** */ /* Both of these return 1 on success, 0 on EOF, <0 on error */ #include "pcap.h" #include "misc_fns.h" static inline uint32_t uint_32_ctx(const struct _pcap_io_ctx *const ctx, const void *v) { return ctx->is_be ? uint_32_be(v) : uint_32_le(v); } static inline uint16_t uint_16_ctx(const struct _pcap_io_ctx *const ctx, const void *v) { return ctx->is_be ? uint_16_be(v) : uint_16_le(v); } // Hi-32, Lo-32 but native within! static inline uint64_t uint_64_be_ctx(const struct _pcap_io_ctx *const ctx, const void *v) { return ((uint64_t)uint_32_ctx(ctx, v) << 32) | (uint64_t)uint_32_ctx(ctx, (const char*)v + 4); } static inline uint64_t uint_64_ctx(const struct _pcap_io_ctx *const ctx, const void *v) { return ctx->is_be ? ((uint64_t)uint_32_be(v) << 32) | (uint64_t)uint_32_be((const uint8_t *)v + 4) : ((uint64_t)uint_32_le((const uint8_t *)v + 4) << 32) | (uint64_t)uint_32_le(v); } static int read_block_header(const struct _pcap_io_ctx *const ctx, uint32_t *const pLength) { uint32_t buf[2]; int rv; *pLength = 0; rv = fread(buf, 8, 1, ctx->file); if (rv != 1) { if (feof(ctx->file)) { return 0; } else { return PCAP_ERR_FILE_READ; } } *pLength = uint_32_ctx(ctx, buf + 1); return uint_32_ctx(ctx, buf + 0); } static int read_chunk(FILE *const f, const size_t len, uint8_t **const pBuf) { int rv; void *buf = malloc(len); *pBuf = NULL; if (buf == NULL) { return PCAP_ERR_OUT_OF_MEMORY; } rv = fread(buf, len, 1, f); if (rv != 1) { free(buf); if (feof(f)) { return 0; } else { return PCAP_ERR_FILE_READ; } } *pBuf = buf; return 1; } static int read_options(FILE *const f, const size_t len, uint8_t **const pBuf) { // If all we have is the final total length data - skip it if (len <= 4) { fseek(f, len, SEEK_CUR); *pBuf = NULL; return 1; } return read_chunk(f, len, pBuf); } typedef enum pcapng_type_e { PCAPNG_TYPE_INVALID_BLOCK = 0, PCAPNG_TYPE_INTERFACE_BLOCK = 1, PCAPNG_TYPE_PACKET_BLOCK = 2, PCAPNG_TYPE_SIMPLE_PACKET_BLOCK = 3, PCAPNG_TYPE_NAME_RESOLUTION_BLOCK = 4, PCAPNG_TYPE_INTERFACE_STATISTICS_BLOCK = 5, PCAPNG_TYPE_ENHANCED_PACKET_BLOCK = 6, PCAPNG_TYPE_SECTION_HEADER_BLOCK = 0x0a0d0d0a } pcapng_type_t; typedef struct pcapng_hdr_packet_s { uint16_t drops_count; uint32_t interface_id; uint32_t captured_len; uint32_t packet_len; uint64_t timestamp; } pcapng_hdr_packet_t; typedef struct pcapng_hdr_section_s { uint16_t major_version; uint16_t minor_version; uint64_t section_length; } pcapng_hdr_section_t; typedef struct pcapng_header_s { pcapng_type_t type; uint8_t *data; uint8_t *options; union { pcapng_hdr_packet_t packet; pcapng_hdr_interface_t iface; pcapng_hdr_section_t section; } hdr; } pcapng_header_t; // Kill header contents static void free_block(pcapng_header_t *const hdr) { hdr->type = PCAPNG_TYPE_INVALID_BLOCK; if (hdr->data != NULL) { free(hdr->data); hdr->data = NULL; } if (hdr->options != NULL) { free(hdr->options); hdr->options = NULL; } } static int do_section_header(struct _pcap_io_ctx *const ctx, uint32_t length, const uint8_t *const buf, pcapng_header_t *const hdr) { uint32_t magic; int rv; // PCAP-NG ctx->is_ng = 1; magic = uint_32_ctx(ctx, buf + 0); printf("Magic = %08x, Len = %#x\n", magic, length); if (magic == 0x1a2b3c4d) { // Right way up } else if (magic == 0x4d3c2b1a) { // Wrong way up ctx->is_be = !ctx->is_be; // Endian reverse length length = (length >> 16) | (length << 16); length = ((length >> 8) & 0xff00ff) | ((length << 8) & 0xff00ff00); } else { return PCAP_ERR_INVALID_MAGIC; } #if SIZEOF_PCAP_HDR_ON_DISC != 24 #error I am confused #endif // Length here includes headers if (length < 28) { return PCAP_ERR_BAD_LENGTH; } length -= 24; hdr->hdr.section.major_version = uint_16_ctx(ctx, buf + 4); hdr->hdr.section.minor_version = uint_16_ctx(ctx, buf + 6); hdr->hdr.section.section_length = uint_64_ctx(ctx, buf + 8); if ((rv = read_options(ctx->file, length, &hdr->options)) <= 0) return rv; return 1; } static int read_block(struct _pcap_io_ctx *const ctx, pcapng_header_t *const hdr) { int rv = 1; int hdr_type; uint32_t length; hdr->type = PCAPNG_TYPE_INVALID_BLOCK; hdr->data = NULL; hdr->options = NULL; if ((hdr_type = read_block_header(ctx, &length)) <= 0) { return hdr_type; } // If section header length may be endian confused so sort in a bit if (hdr_type != PCAPNG_TYPE_SECTION_HEADER_BLOCK) { if (length > 0x100000 || length < 8) { return PCAP_ERR_BAD_LENGTH; } length -= 8; } switch (hdr_type) { case PCAPNG_TYPE_INTERFACE_BLOCK: { uint8_t buf[8]; if (length < 12) return PCAP_ERR_BAD_LENGTH; if (fread(buf, 8, 1, ctx->file) != 1) return PCAP_ERR_FILE_READ; hdr->hdr.iface.link_type = uint_16_ctx(ctx, buf + 0); hdr->hdr.iface.snap_len = uint_32_ctx(ctx, buf + 4); if ((rv = read_options(ctx->file, length - 8, &hdr->options)) <= 0) return rv; // Now stash - cos we need it later // Alloc a new if (or at least check we have one) if (ctx->if_count + 1 > ctx->if_size) { if (ctx->interfaces == NULL) { if ((ctx->interfaces = malloc(sizeof(*ctx->interfaces) * 4)) == NULL) return PCAP_ERR_OUT_OF_MEMORY; ctx->if_size = 4; } else { pcapng_hdr_interface_t *resized = realloc(ctx->interfaces, sizeof(*ctx->interfaces) * ctx->if_size * 2); if (resized == NULL) return PCAP_ERR_OUT_OF_MEMORY; ctx->if_size *= 2; ctx->interfaces = resized; } } ctx->interfaces[ctx->if_count++] = hdr->hdr.iface; break; } case PCAPNG_TYPE_PACKET_BLOCK: case PCAPNG_TYPE_ENHANCED_PACKET_BLOCK: { uint8_t buf[20]; size_t data_len; if (length < 24) return PCAP_ERR_BAD_LENGTH; if (fread(buf, 20, 1, ctx->file) != 1) return PCAP_ERR_FILE_READ; if (hdr_type == PCAPNG_TYPE_PACKET_BLOCK) { hdr->hdr.packet.interface_id = uint_16_ctx(ctx, buf + 0); hdr->hdr.packet.drops_count = uint_16_ctx(ctx, buf + 2); } else { hdr->hdr.packet.interface_id = uint_32_ctx(ctx, buf + 0); hdr->hdr.packet.drops_count = 0; } hdr->hdr.packet.timestamp = uint_64_be_ctx(ctx, buf + 4); hdr->hdr.packet.captured_len = uint_32_ctx(ctx, buf + 12); hdr->hdr.packet.packet_len = uint_32_ctx(ctx, buf + 16); if (hdr->hdr.packet.interface_id >= ctx->if_count) return PCAP_ERR_BAD_INTERFACE_ID; length -= 20; data_len = (hdr->hdr.packet.captured_len + 3) & ~3; if (length - 4 < data_len) return PCAP_ERR_BAD_LENGTH; if ((rv = read_chunk(ctx->file, data_len, &hdr->data)) <= 0) break; length -= data_len; if ((rv = read_options(ctx->file, length, &hdr->options)) <= 0) break; break; } case PCAPNG_TYPE_SECTION_HEADER_BLOCK: { uint8_t buf[16]; // Clear out old data even if we error // All interfaces are toast if (ctx->interfaces != NULL) { free(ctx->interfaces); ctx->interfaces = NULL; ctx->if_count = 0; ctx->if_size = 0; } if (fread(buf, 16, 1, ctx->file) != 1) return PCAP_ERR_FILE_READ; if ((rv = do_section_header(ctx, length, buf, hdr)) < 0) return rv; break; } default: fseek(ctx->file, length, SEEK_CUR); break; } if (rv <= 0) { free_block(hdr); } else { hdr->type = hdr_type; } return rv; } static int pcap_read_header(PCAP_reader_p ctx, pcap_hdr_t *hdr) { uint8_t hdr_val[SIZEOF_PCAP_HDR_ON_DISC]; int rv; uint32_t magic; // This reads an old-style header which is shorter than the shortest new-style one rv = fread(&hdr_val[0], SIZEOF_PCAP_HDR_ON_DISC, 1, ctx->file); if (rv != 1) { if (feof(ctx->file)) { return 0; } else { return PCAP_ERR_FILE_READ; } } magic = uint_32_be(hdr_val + 0); if (magic == PCAPNG_TYPE_SECTION_HEADER_BLOCK) { pcapng_header_t nghdr = { 0 }; printf("NG header found\n"); // PCAP-NG ctx->is_ng = 1; if ((rv = do_section_header(ctx, uint_32_ctx(ctx, hdr_val + 4), hdr_val + 8, &nghdr)) <= 0) return rv; hdr->magic_number = 0x1a2b3c4d; hdr->version_major = nghdr.hdr.section.major_version; hdr->version_minor = nghdr.hdr.section.minor_version; printf("Version: %d.%d\n", hdr->version_major, hdr->version_minor); // Find the 1st i/f block (there must be one before the data) for (;;) { free_block(&nghdr); if ((rv = read_block(ctx, &nghdr)) <= 0) { return rv; } if (nghdr.type == PCAPNG_TYPE_INTERFACE_BLOCK) { hdr->snaplen = nghdr.hdr.iface.snap_len; hdr->network = nghdr.hdr.iface.link_type; free_block(&nghdr); break; } } } else { ctx->is_ng = 0; /* The magic number is 0xa1b2c3d4. If the writing * machine was BE, the first byte will be a1 else d4 */ if (magic == 0xa1b2c3d4) { // Big endian. ctx->is_be = 1; } else if (magic == 0xd4c3b2a1) { // Little endian. ctx->is_be = 0; } else { return PCAP_ERR_INVALID_MAGIC; } hdr->magic_number = 0xa1b2c3d4; hdr->version_major = (ctx->is_be ? uint_16_be(&hdr_val[4]) : uint_16_le(&hdr_val[4])); hdr->version_minor = (ctx->is_be ? uint_16_be(&hdr_val[6]) : uint_16_le(&hdr_val[6])); hdr->thiszone = (int32_t)(ctx->is_be ? uint_32_be(&hdr_val[8]) : uint_32_le(&hdr_val[8])); hdr->sigfigs = (ctx->is_be ? uint_32_be(&hdr_val[12]) : uint_32_le(&hdr_val[12])); hdr->snaplen = (ctx->is_be ? uint_32_be(&hdr_val[16]) : uint_32_le(&hdr_val[16])); hdr->network = (ctx->is_be ? uint_32_be(&hdr_val[20]) : uint_32_le(&hdr_val[20])); } return 1; } static int pcap_read_pktheader(PCAP_reader_p ctx, pcaprec_hdr_t *hdr) { uint8_t hdr_val[SIZEOF_PCAPREC_HDR_ON_DISC]; int rv; rv = fread(&hdr_val[0], SIZEOF_PCAPREC_HDR_ON_DISC, 1, ctx->file); if (rv != 1) { if (feof(ctx->file)) { return 0; } else { return PCAP_ERR_FILE_READ; } } hdr->ts_sec = (ctx->is_be ? uint_32_be(&hdr_val[0]) : uint_32_le(&hdr_val[0])); hdr->ts_usec = (ctx->is_be ? uint_32_be(&hdr_val[4]) : uint_32_le(&hdr_val[4])); hdr->incl_len = (ctx->is_be ? uint_32_be(&hdr_val[8]) : uint_32_le(&hdr_val[8])); hdr->orig_len = (ctx->is_be ? uint_32_be(&hdr_val[12]) : uint_32_le(&hdr_val[12])); return 1; } extern int pcap_open(PCAP_reader_p *ctx_p, pcap_hdr_t *out_hdr, const char *filename) { FILE *fptr = (filename ? fopen(filename, "rb") : stdin); PCAP_reader_p ctx; int rv; (*ctx_p) = NULL; if (!fptr) { // Couldn't open the file. return -1; } ctx = (PCAP_reader_p)calloc(SIZEOF_PCAP_READER, 1); if (!ctx) { fclose(fptr); // Out of memory. return -2; } ctx->file = fptr; rv = pcap_read_header(ctx, out_hdr); if (rv != 1) { // Header read failed. fclose(ctx->file); free(ctx); return -4; } (*ctx_p) = ctx; return 0; } extern int pcap_read_next(PCAP_reader_p ctx, pcaprec_hdr_t *out_hdr, uint8_t **out_data, uint32_t *out_len) { int rv; (*out_data) = NULL; (*out_len) = 0; if (ctx->is_ng) { for (;;) { pcapng_header_t nghdr; if ((rv = read_block(ctx, &nghdr)) <= 0) { return rv; } if (nghdr.type == PCAPNG_TYPE_PACKET_BLOCK || nghdr.type == PCAPNG_TYPE_ENHANCED_PACKET_BLOCK) { *out_data = nghdr.data; *out_len = nghdr.hdr.packet.captured_len; out_hdr->incl_len = nghdr.hdr.packet.captured_len; out_hdr->orig_len = nghdr.hdr.packet.packet_len; out_hdr->ts_sec = (uint32_t)(nghdr.hdr.packet.timestamp / 1000000); out_hdr->ts_usec = (uint32_t)(nghdr.hdr.packet.timestamp % 1000000); // NULL out so we don't free it here! nghdr.data = NULL; free_block(&nghdr); return 1; } free_block(&nghdr); } } else { rv = pcap_read_pktheader(ctx, out_hdr); if (rv != 1) {return rv; } // Otherwise we now know how long our packet is .. (*out_data) = (uint8_t*)malloc(out_hdr->incl_len); if (!(*out_data)) { // Out of memory. return -3; } (*out_len) = out_hdr->incl_len; rv = fread((*out_data), (*out_len), 1, ctx->file); if (rv != 1) { free(*out_data); (*out_data) = NULL; *out_len = 0; if (feof(ctx->file)) { // Ah. EOF. return 0; } else { // Error. Curses. return rv; } } else if (rv == 1) { // Gotcha. return 1; } } return 0; } int pcap_close(PCAP_reader_p *const pctx) { PCAP_reader_p ctx = *pctx; if (ctx == NULL) return 0; if (ctx->interfaces != NULL) { free(ctx->interfaces); } if (ctx->file != NULL) { fclose(ctx->file); } free(ctx); return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pcap.h000066400000000000000000000075131261471605300157720ustar00rootroot00000000000000/* pcap.h */ /* * Read pcap files * * Documentation from * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Richard Watts, Kynesim * * ***** END LICENSE BLOCK ***** */ #ifndef _pcap_h #define _pcap_h #include #include #include "compat.h" //! Out of memory. #define PCAP_ERR_OUT_OF_MEMORY (-8) //! File read error #define PCAP_ERR_FILE_READ (-9) //! Invalid magic #define PCAP_ERR_INVALID_MAGIC (-10) #define PCAP_ERR_BAD_LENGTH (-11); #define PCAP_ERR_BAD_INTERFACE_ID (-12); /*! File header */ typedef struct pcap_hdr_s { /*! Magic number - 0xa1b2c3d4 means no swap needed, * 0xd4c3b2a1 means we'll need to swap. */ uint32_t magic_number; /*! Major version number (currently 2) */ uint16_t version_major; /*! Minor version number (4 + ) */ uint16_t version_minor; /*! GMT to local-time correction, in s */ int32_t thiszone; /*! Accuracy of timestamps. In practice, always 0 */ uint32_t sigfigs; /*! Snapshot length (typically 65535 + but might be limited) */ uint32_t snaplen; /* These are network types - equivalent to WTAP_ENCAP_XXX in * libpcap.c . We only care about a few .. */ #define PCAP_NETWORK_TYPE_NONE 0 #define PCAP_NETWORK_TYPE_ETHERNET 1 /*! Network type: Ethernet = 1 .. */ uint32_t network; } pcap_hdr_t; #define SIZEOF_PCAP_HDR_ON_DISC (4 + 2 + 2 + 4 + 4 + 4 + 4) /*! Packet header */ typedef struct pcaprec_hdr_s { /*! Timestamp seconds */ uint32_t ts_sec; /*! Timetamp uS */ uint32_t ts_usec; /*! Number of octets saved after the header */ uint32_t incl_len; /*! Original packet length */ uint32_t orig_len; } pcaprec_hdr_t; #define SIZEOF_PCAPREC_HDR_ON_DISC (4 + 4 + 4 + 4) typedef struct pcapng_hdr_interface_s { uint16_t link_type; uint32_t snap_len; } pcapng_hdr_interface_t; /*! Used to store I/O parameters for pcap I/O */ typedef struct _pcap_io_ctx { // pcap or pcapng? int is_ng; /*! Endianness of the file */ int is_be; /*! The FILE* for this file */ FILE *file; uint32_t if_count; uint32_t if_size; pcapng_hdr_interface_t * interfaces; } PCAP_reader_t; typedef struct _pcap_io_ctx *PCAP_reader_p; #define SIZEOF_PCAP_READER sizeof(struct _pcap_io_ctx) /*! Attempt to open a pcap file and read the header. * * \param filename IN Filename or NULL for stdin. * \return 0 on success, non-zero on failure. */ int pcap_open(PCAP_reader_p *ctx_p, pcap_hdr_t *out_hdr, const char *filename); /*! Read the next packet from a pcap file. The returned data is * malloc()d and must be free()d. If we fail, returned data will * be NULL. * * \return 1 on success, 0 if we've reached EOF, < 0 on error. */ int pcap_read_next(PCAP_reader_p ctx_p, pcaprec_hdr_t *out_hdr, uint8_t **out_data, uint32_t *out_len); /*! Close the pcap file */ int pcap_close(PCAP_reader_p * const ctx_p); #endif // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pcapreport.c000066400000000000000000001616121261471605300172220ustar00rootroot00000000000000/* * Report on a pcap (.pcap) file. * * 2008-09-05 * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Richard Watts, Kynesim * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #include #include #include #endif // _WIN32 #include "compat.h" #include "pcap.h" #include "ethernet.h" #include "ipv4.h" #include "version.h" #include "misc_fns.h" #include "ts_fns.h" #include "fmtx.h" typedef struct pcapreport_stream_struct pcapreport_stream_t; #define JITTER_BUF_SIZE 1024 typedef struct jitter_el_struct { uint32_t t; int delta; } jitter_el_t; typedef struct jitter_env_struct { int min_val; int max_val; int in_n; int out_n; int len; jitter_el_t buf[JITTER_BUF_SIZE]; } jitter_env_t; typedef struct pcapreport_section_struct pcapreport_section_t; struct pcapreport_section_struct { pcapreport_section_t * next; unsigned int section_no; unsigned int pcr_count; unsigned int jitter_max; uint32_t pkt_start; uint32_t pkt_final; uint64_t time_start; // 90kHz uint64_t time_first; // time @ first PCR uint64_t time_last; // time @ last PCR uint64_t time_final; uint64_t pcr_start; // 90kHz uint64_t pcr_last; int64_t skew_last; int64_t skew_min; int64_t skew_max; uint64_t ts_byte_start; uint64_t ts_byte_final; int32_t rtp_skew_min; int32_t rtp_skew_max; }; typedef struct pcapreport_vlan_info_s { uint16_t vid; uint16_t cfimap; uint16_t pcpmap; } pcapreport_vlan_info_t; typedef struct pcapreport_rtp_info_s { uint16_t last_seq; uint32_t n; uint32_t ssrc; int multiple_ssrc; } pcapreport_rtp_info_t; // RTP info (if any) in a packet typedef struct rtp_header_s { int is_rtp_ts; int is_rtp_raw; int marker; uint8_t payload_type; uint16_t sequence_number; uint32_t timestamp; uint32_t ssrc; uint32_t header_len; uint32_t pad_len; // CSRC ignored // Extension ignored } rtp_header_t; struct pcapreport_stream_struct { pcapreport_stream_t * hash_next; const char *output_name; FILE *output_file; uint32_t output_dest_addr; uint32_t output_dest_port; FILE * csv_file; const char * csv_name; int stream_no; int force; // We have an explicit filter - try harder int ts_good; // Not a boolean -ve is bad, +ve is good int seen_good; // Includes those seen_dodgy int seen_bad; int seen_dodgy; // Count of packets that we aren't completely happy with but have declared good int multiple_pcr_pids; TS_reader_p ts_r; uint32_t pcr_pid; // The temporary read buffer used by our ts reader. byte *tmp_buf; uint32_t tmp_len; // ts packet counter for error reporting. uint32_t ts_counter; // Count overlength packets uint32_t pkts_overlength; /*! How far do we need to skew (in 90kHz units) to signal a discontinuity? */ int64_t skew_discontinuity_threshold; int64_t last_time_offset; uint64_t ts_bytes; pcapreport_section_t * section_first; pcapreport_section_t * section_last; int vlan_count; pcapreport_vlan_info_t vlans[ETHERNET_VLANS_MAX]; pcapreport_rtp_info_t rtp_info; jitter_env_t jitter; }; typedef struct pcapreport_fragment_struct { int in_use; uint16_t ident; uint16_t current_len; byte pkt[65536]; } pcapreport_fragment_t; typedef struct pcapreport_reassembly_struct { pcapreport_fragment_t frag; } pcapreport_reassembly_t; typedef struct pcapreport_ctx_struct { int use_stdin; char *input_name; const char * base_name; int had_input_name; int extract_data; int dump_data; int dump_extra; int time_report; int verbose; int analyse; int extract; int stream_count; int csv_gen; int good_ts_only; // Only keep good pkts int keep_bad; // Keep all packets (inc bad) int file_split_section; PCAP_reader_p pcreader; pcap_hdr_t pcap_hdr; unsigned int tfmt; // packet counter. uint32_t pkt_counter; uint32_t filter_dest_addr; uint32_t filter_dest_port; const char * output_name_base; int64_t opt_skew_discontinuity_threshold; uint64_t time_start; // 90kHz uint32_t time_usec; time_t time_sec; uint8_t rtp_raw_wanted[256]; pcapreport_stream_t * stream_hash[256]; pcapreport_reassembly_t reassembly_env; } pcapreport_ctx_t; static unsigned int jitter_value(const jitter_env_t * const je) { return je->max_val - je->min_val; } static unsigned int jitter_add(jitter_env_t * const je, const int delta, const uint32_t time, const uint32_t range) { jitter_el_t * const eob = je->buf + JITTER_BUF_SIZE; jitter_el_t * const in_el = je->buf + je->in_n; jitter_el_t * out_el = je->buf + je->out_n; jitter_el_t * const next_el = (je->in_n == JITTER_BUF_SIZE - 1) ? je->buf : in_el + 1; int needs_scan = FALSE; // 1st expire anything we no longer want - in any case expire one if // we are about to overflow. while (in_el != out_el && (time - out_el->t > range || out_el == next_el)) { if (out_el->delta == je->min_val || out_el->delta == je->max_val) needs_scan = TRUE; // Inc with wrap if (++out_el >= eob) out_el = je->buf; } if (needs_scan || in_el == out_el) { // Only recalc max & min for the buffer if we have expired a previous one // also if empty then must force both to delta which this code will do const jitter_el_t * el = out_el; int min_val = delta; int max_val = delta; while (el != in_el) { if (el->delta > max_val) max_val = el->delta; if (el->delta < min_val) min_val = el->delta; if (++el >= eob) el = je->buf; } je->max_val = max_val; je->min_val = min_val; } else { // Otherwise check to see if this is a new max/min based on old values if (delta > je->max_val) je->max_val = delta; if (delta < je->min_val) je->min_val = delta; } // Now add to the end in_el->t = time; in_el->delta = delta; // and update the environment je->in_n = next_el - je->buf; je->out_n = out_el - je->buf; return jitter_value(je); } static void jitter_clear(jitter_env_t * const je) { je->in_n = 0; je->out_n = 0; je->max_val = 0; je->min_val = 0; } static uint64_t pkt_time(const pcaprec_hdr_t * const pcap_pkt_hdr) { return (((int64_t)pcap_pkt_hdr->ts_usec*9)/100) + ((int64_t)pcap_pkt_hdr->ts_sec * 90000); } static char * vlan_name(const char * prefix, const pcapreport_stream_t * const st, const size_t blen, char * const buf) { if (st->vlan_count == 0) { buf[0] = '\0'; } else { int i; size_t n = strlen(prefix); char * p = buf; char * const eob = buf + blen; memcpy(p, prefix, n); p += n; for (i = 0; i < st->vlan_count && eob - p > 2; ++i) { const pcapreport_vlan_info_t * const vi = st->vlans + i; if (i != 0) *p++ = '.'; p += snprintf(p, eob - p, "%d", vi->vid); } } return buf; } static char * section_name(const pcapreport_ctx_t * const ctx, const pcapreport_stream_t * const st, char * const pbuf, const size_t pbuf_len) { if (!ctx->file_split_section) { *pbuf = '\0'; return pbuf; } snprintf(pbuf, pbuf_len, "_S%d", st->section_last == NULL ? 0 : st->section_last->section_no); return pbuf; } static void stream_gen_names2(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const rtp_header_t * const rtp_header) { const uint32_t dest_addr = st->output_dest_addr; const uint32_t dest_port = st->output_dest_port; char pbuf[32], pbuf2[32]; char identifier[64]; const char * const base_name = ctx->output_name_base != NULL ? ctx->output_name_base : ctx->base_name; const size_t base_len = strlen(base_name); int fixed_extract_name = FALSE; if (ctx->filter_dest_addr == 0 || ctx->filter_dest_port == 0) { snprintf(identifier, 64, "%s_%u.%u.%u.%u_%u%s", vlan_name("_V", st, sizeof(pbuf), pbuf), dest_addr >> 24, (dest_addr >> 16) & 0xff, (dest_addr >> 8) & 0xff, dest_addr & 0xff, dest_port, section_name(ctx, st, pbuf2, sizeof(pbuf2))); } else { identifier[0] = '\0'; // If we have been given a unique filter and a name then assume they // actually want that name! fixed_extract_name = (ctx->output_name_base != NULL); } if (ctx->extract) { char * name = malloc(base_len + 64); memcpy(name, base_name, base_len + 1); if (!fixed_extract_name) snprintf(name + base_len, 64, "%s.%s", identifier, (rtp_header != NULL && rtp_header->is_rtp_raw) ? "rtp" : "ts"); st->output_name = name; } if (ctx->csv_gen) { char * name = malloc(base_len + 64); memcpy(name, ctx->base_name, base_len); snprintf(name + base_len, 64, "%s.csv", identifier); st->csv_name = name; } } static void stream_gen_names(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const rtp_header_t * const rtp_header) { // Only bother if there is some reason if (ctx->extract || ctx->csv_gen) stream_gen_names2(ctx, st, rtp_header); } static void stream_close_files(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st) { if (st->output_file != NULL) { if (st->seen_dodgy != 0) { fprint_msg(">%d> WARNING: %d dodgy packet%s written to: %s\n", st->stream_no, st->seen_dodgy, st->seen_dodgy == 1 ? "" : "s", st->output_name); } if (st->seen_bad != 0) { fprint_msg(">%d> WARNING: %d bad packet%s excluded from: %s\n", st->stream_no, st->seen_bad, st->seen_bad == 1 ? "" : "s", st->output_name); } fclose(st->output_file); st->output_file = NULL; } if (st->csv_file != NULL) { fclose(st->csv_file); st->csv_file = NULL; } } static pcapreport_section_t * section_create(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const pcaprec_hdr_t * const pcap_pkt_hdr) { pcapreport_section_t * const tsect = calloc(1, sizeof(*tsect)); pcapreport_section_t * const last = st->section_last; if (ctx->file_split_section) stream_close_files(ctx, st); if (tsect == NULL) return NULL; // Bind into stream if (last == NULL) { // Empty chain - add as first el st->section_first = tsect; } else { // Add to end tsect->section_no = last->section_no + 1; last->next = tsect; } st->section_last = tsect; // Init "obvious" non-zero stuff tsect->rtp_skew_max = -0x7fffffff; tsect->rtp_skew_min = 0x7fffffff; tsect->skew_max = -0x7fffffff; tsect->skew_min = 0x7fffffff; tsect->time_final = tsect->time_start = pkt_time(pcap_pkt_hdr); tsect->pkt_final = tsect->pkt_start = ctx->pkt_counter; tsect->ts_byte_start = tsect->ts_byte_final = st->ts_bytes; if (ctx->file_split_section || last == NULL) stream_gen_names(ctx, st, NULL); return tsect; } // Discontinuity threshold is 6s. #define SKEW_DISCONTINUITY_THRESHOLD (6*90000) static int digest_times_read(void *handle, byte *out_buf, size_t len) { pcapreport_stream_t * const st = handle; int nr_bytes = (len < st->tmp_len ? len : st->tmp_len); int new_tmp_len = st->tmp_len - nr_bytes; memcpy(out_buf, st->tmp_buf, nr_bytes); memmove(st->tmp_buf, &st->tmp_buf[nr_bytes], new_tmp_len); st->tmp_len = new_tmp_len; // fprint_msg(">> read %d bytes from intermediate buffer. \n", nr_bytes); return nr_bytes; } static int digest_times_seek(void *handle, offset_t val) { // Cannot seek in a ts stream. return 1; } // 33 bit comparison static int64_t pts_diff(const uint64_t a, const uint64_t b) { return ((int64_t)(a - b) << 31) >> 31; } static int64_t pts_diff_abs(const uint64_t a, const uint64_t b) { const int64_t t = pts_diff(a, b); return t < 0 ? -t : t; } static int digest_times(pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const pcaprec_hdr_t * const pcap_pkt_hdr, const ethernet_packet_t * const epkt, const ipv4_header_t * const ipv4_header, const ipv4_udp_header_t * const udp_header, const rtp_header_t * const rtp_header, const byte * const data, const uint32_t len) { int rv; unsigned int rtp_seq_delta = 0; // Deal with RTP contents - currently held with stream but could be moved to section // especially if we do more timestamp analysis if (rtp_header->is_rtp_ts) { pcapreport_rtp_info_t * const ri = &st->rtp_info; if (ri->ssrc != rtp_header->ssrc && ri->n != 0 && !ri->multiple_ssrc) { fprint_msg("!%d! Multiple SSRCs detected: SSRCs: %u,%u,...\n", st->stream_no, ri->ssrc, rtp_header->ssrc); ri->multiple_ssrc = TRUE; } rtp_seq_delta = ri->n == 0 ? 0 : (rtp_header->sequence_number - (ri->last_seq + 1)) & 0xffffU; if (rtp_seq_delta != 0) { fprint_msg("!%d! @%u: RTP seq delta (%u->%u) != 1\n", st->stream_no, ctx->pkt_counter, ri->last_seq, rtp_header->sequence_number); } ++ri->n; ri->ssrc = rtp_header->ssrc; ri->last_seq = rtp_header->sequence_number; } if (st->ts_r == NULL) { rv = build_TS_reader_with_fns(st, digest_times_read, digest_times_seek, &st->ts_r); if (rv) { print_err( "### pcapreport: Cannot create ts reader.\n"); return 1; } } // Add all our data to the pool. { unsigned int pkts = len / TS_PACKET_SIZE; unsigned int pktlen = pkts * TS_PACKET_SIZE; if (pktlen != len) ++st->pkts_overlength; st->tmp_buf = (byte *)realloc(st->tmp_buf, st->tmp_len + pktlen); memcpy(&st->tmp_buf[st->tmp_len], data, pktlen); st->tmp_len += pktlen; } // Now read out all the ts packets we can. while (1) { byte *pkt; int rv; rv = read_next_TS_packet(st->ts_r, &pkt); if (rv == EOF) { // Got to EOF - return for more data return 0; } // Right. Split it .. { const uint64_t t_pcr = pkt_time(pcap_pkt_hdr); uint32_t pid; int pusi; byte *adapt; int adapt_len; byte *payload; int payload_len; rv = split_TS_packet(pkt, &pid, &pusi, &adapt, &adapt_len, &payload, &payload_len); if (rv) { fprint_msg(">%d> WARNING: TS packet %d [ packet %d @ %d.%d s ] cannot be split.\n", st->stream_no, st->ts_counter, ctx->pkt_counter, pcap_pkt_hdr->ts_sec, pcap_pkt_hdr->ts_usec); } else { //int cc; // PCR ? if (adapt && adapt_len) { int has_pcr; uint64_t pcr; int64_t pcr_time_offset; get_PCR_from_adaptation_field(adapt, adapt_len, &has_pcr, &pcr); if (has_pcr) { int64_t skew; if (ctx->time_report) { fprint_msg(">%d> Found PCR %lld at %d.%d s \n", st->stream_no, pcr, pcap_pkt_hdr->ts_sec, pcap_pkt_hdr->ts_usec); } if (st->pcr_pid == 0) st->pcr_pid = pid; if (pid != st->pcr_pid) { // *** If this happens often then fix to track each Pid if (!st->multiple_pcr_pids) { fprint_msg("!%d! Multiple PCR pids detected: pids: %d,%d,...\n", st->stream_no, st->pcr_pid, pid); } st->multiple_pcr_pids = TRUE; } else { pcapreport_section_t * tsect = st->section_last; unsigned int cur_jitter; // PCR pops out in 27MHz units. Let's do all our comparisons // in 90kHz. pcr /= 300; // fprint_msg("pcr = %lld t_pcr = %lld diff = %lld\n", // pcr, t_pcr, t_pcr - pcr); pcr_time_offset = pts_diff(t_pcr, pcr); skew = tsect->pcr_count == 0 ? 0LL : pcr_time_offset - pts_diff(tsect->time_first, tsect->pcr_start); // Change section if discontinuity too big if (st->skew_discontinuity_threshold > 0 && tsect->pcr_count != 0) { const int64_t pcr_delta = pts_diff_abs(pcr, tsect->pcr_last); const int64_t time_delta = pts_diff_abs(t_pcr, tsect->time_last); const int64_t skew_delta = skew - tsect->skew_last; if (pcr_delta > st->skew_discontinuity_threshold || time_delta > st->skew_discontinuity_threshold || skew_delta > st->skew_discontinuity_threshold) { section_create(ctx, st, pcap_pkt_hdr); tsect = st->section_last; } } if (tsect->pcr_count == 0) { if (tsect->section_no != 0) { fprint_msg(">%d> Skew discontinuity! Skew = %lld (> %lld) at" " ts = %d network = %d (PCR %lld Time %d.%d)\n", st->stream_no, skew, st->skew_discontinuity_threshold, st->ts_counter, ctx->pkt_counter, pcr, pcap_pkt_hdr->ts_sec, pcap_pkt_hdr->ts_usec); } tsect->pkt_final = ctx->pkt_counter; tsect->pcr_last = tsect->pcr_start = pcr; tsect->time_last = tsect->time_first = t_pcr; jitter_clear(&st->jitter); skew = 0; st->last_time_offset = 0; } // Extract jitter over up to the last 10s. skew will be within // an int by now cur_jitter = jitter_add(&st->jitter, (int)skew, (uint32_t)(t_pcr & 0xffffffffU), 90000 * 10); if (tsect->skew_max < skew) tsect->skew_max = skew; if (tsect->skew_min > skew) tsect->skew_min = skew; if (tsect->jitter_max < cur_jitter) tsect->jitter_max = cur_jitter; if (rtp_header->is_rtp_ts) { // We have both PCR & RTP times - look for min & max int32_t rtp_skew = (int32_t)(rtp_header->timestamp - (uint32_t)(t_pcr & 0xffffffffU)); if (tsect->rtp_skew_max < rtp_skew) tsect->rtp_skew_max = rtp_skew; if (tsect->rtp_skew_min > rtp_skew) tsect->rtp_skew_min = rtp_skew; } if (ctx->time_report) { int64_t rel_tim = t_pcr - tsect->time_first; // 90kHz double skew_rate = (rel_tim == 0) ? 0.0 : (double)skew / ((double)((double)rel_tim / (60*90000))); fprint_msg(">%d> [ts %d net %d ] PCR %lld Time %d.%d [rel %d.%d] - skew = %lld (delta = %lld, rate = %.4g PTS/min) - jitter=%u\n", st->stream_no, st->ts_counter, ctx->pkt_counter, pcr, pcap_pkt_hdr->ts_sec, pcap_pkt_hdr->ts_usec, (int)(rel_tim / (int64_t)1000000), (int)rel_tim%1000000, skew, pcr_time_offset - st->last_time_offset, skew_rate, cur_jitter); } if (st->csv_name != NULL) // We should be outputting to file { if (st->csv_file == NULL) { if ((st->csv_file = fopen(st->csv_name, "wt")) == NULL) { fprint_err("### pcapreport: Cannot open %s .\n", st->csv_name); exit(1); } fprintf(st->csv_file, "\"PKT\",\"Time\",\"PCR\",\"Skew\",\"Jitter\"\n"); } fprintf(st->csv_file, "%d," LLU_FORMAT "," LLU_FORMAT "," LLD_FORMAT ",%u\n", ctx->pkt_counter, t_pcr - ctx->time_start, pcr, skew, cur_jitter); } // Remember where we are for posterity tsect->pcr_last = pcr; tsect->time_last = t_pcr; tsect->skew_last = skew; st->last_time_offset = pcr_time_offset; ++tsect->pcr_count; } } } } // Actions at end of TS packet ++st->ts_counter; st->ts_bytes += TS_PACKET_SIZE; { pcapreport_section_t * const tsect = st->section_last; if (tsect != NULL) { tsect->time_final = t_pcr; tsect->ts_byte_final = st->ts_bytes; tsect->pkt_final = ctx->pkt_counter; } } } } } static int write_out_packet(pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const byte *data, const uint32_t len) { int rv; unsigned int pkts = len / 188; if (st->output_name) { if (st->output_file == NULL) { fprint_msg("pcapreport: Dumping %s packets for %s:%d to %s\n", ctx->good_ts_only ? "good ts" : ctx->keep_bad ? "all" : "ts", ipv4_addr_to_string(st->output_dest_addr), st->output_dest_port, st->output_name); st->output_file = fopen(st->output_name, "wb"); if (!st->output_file) { fprint_err("### pcapreport: Cannot open %s .\n", st->output_name); return 1; } } if (ctx->verbose) { fprint_msg("++ Dumping %d bytes to output file.\n", len); } rv = fwrite(data, 188, pkts, st->output_file); if (rv != pkts) { fprint_err( "### pcapreport: Couldn't write %d bytes" " to %s (error = %d).\n", len, st->output_name, ferror(st->output_file)); return 1; } } return 0; } static int stream_ts_check(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const byte * const data, const uint32_t len) { const byte * ptr; int good = 0; int bad = 0; if (st->force) st->ts_good = 10; if (len % 188 != 0) ++bad; else ++good; for (ptr = data; ptr < data + len; ptr += 188) { if (*ptr != 0x47) ++bad; else ++good; } st->ts_good += good - bad; if (st->ts_good > 10) st->ts_good = 10; if (st->ts_good < -10) st->ts_good = -10; if (st->ts_good <= 0 || (bad != 0 && ctx->good_ts_only)) { ++st->seen_bad; return FALSE; } if (bad != 0) ++st->seen_dodgy; ++st->seen_good; return TRUE; } // RTP - RFC 3550 // RTP payload types - RFC 3551 // M2TS - RFC 2250 static int write_rtp_raw_packet(pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const byte *data, const uint32_t len) { if (st->output_name) { int rv; if (st->output_file == NULL) { fprint_msg("pcapreport: Dumping raw RTP packets for %s:%d to %s\n", ipv4_addr_to_string(st->output_dest_addr), st->output_dest_port, st->output_name); st->output_file = fopen(st->output_name, "wb"); if (!st->output_file) { fprint_err("### pcapreport: Cannot open %s .\n", st->output_name); return 1; } } if (ctx->verbose) { fprint_msg("++ Dumping %d bytes to output file.\n", len); } // need header { byte hdr[8]; hdr[0] = 'R'; hdr[1] = 'T'; hdr[2] = 'P'; hdr[3] = ' '; hdr[4] = (len >> 24) & 0xff; hdr[5] = (len >> 16) & 0xff; hdr[6] = (len >> 8) & 0xff; hdr[7] = len & 0xff; rv = fwrite(hdr, sizeof(hdr), 1, st->output_file); if (rv != 1) { fprint_err( "### pcapreport: Couldn't write RTP hdr bytes" " to %s (error = %d).\n", st->output_name, ferror(st->output_file)); return 1; } } rv = fwrite(data, 1, len, st->output_file); if (rv != len) { fprint_err( "### pcapreport: Couldn't write %d bytes" " to %s (error = %d).\n", len, st->output_name, ferror(st->output_file)); return 1; } } return 0; } static int stream_rtp_check(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const byte * const data, const uint32_t len, rtp_header_t * const rh) { uint32_t offset; uint32_t padlen = 0; unsigned int payload_type; int is_raw = FALSE; // Flatten output memset(rh, 0, sizeof(*rh)); // Must contain at least the header! if (len < 12) return FALSE; // Check version - must be 2 // Incidentally this will reject 0x47 which is good :-) if ((data[0] & 0xc0) != 0x80) return FALSE; // We only deal with TS in RTP so check for that alone payload_type = data[1] & 0x7f; if (ctx->rtp_raw_wanted[payload_type] != 0) is_raw = TRUE; else if ((data[1] & 0x7f) != 33) // PT bits return FALSE; // ??Check sequence?? // offset = start of extension or payload offset = 12 + (data[0] & 0xf) * 4; // Check for padding if ((data[0] & 0x20) != 0) // P bit { padlen = data[len - 1]; // Padlen of zero makes no sense as padding len includes this byte if (padlen == 0) return FALSE; } // Check for extension if ((data[0] & 0x10) != 0) // X bit { if (offset + 4 + padlen > len) return FALSE; // Skip extension header offset += 4 + uint_16_be(data + offset + 2); } // trivial check for TS in payload if not raw extraction if (!is_raw && (offset + 188 + padlen > len || data[offset] != 0x47)) return FALSE; rh->is_rtp_raw = is_raw; rh->is_rtp_ts = !is_raw; rh->marker = ((data[1] & 0x80) != 0); rh->payload_type = (uint8_t)payload_type; rh->sequence_number = uint_16_be(data + 2); rh->timestamp = uint_32_be(data + 4); rh->ssrc = uint_32_be(data + 8); rh->header_len = offset; rh->pad_len = padlen; return TRUE; } static void stream_merge_vlan_info(pcapreport_stream_t * const st, const ethernet_packet_t * const epkt) { int i; for (i = 0; i < epkt->vlan_count; ++i) { st->vlans[i].cfimap |= (1 << epkt->vlans[i].cfi); st->vlans[i].pcpmap |= (1 << epkt->vlans[i].pcp); } } // Close the stream // Closes any extraction file(s) & frees associated memory // Replaces contents of passed stream pointer with next in hash chain void stream_close(pcapreport_ctx_t * const ctx, pcapreport_stream_t ** pst) { pcapreport_stream_t * const st = *pst; // Set pointer to next in chain *pst = st->hash_next; { // Free off all our section data pcapreport_section_t * p = st->section_first; while (p != NULL) { pcapreport_section_t * np = p->next; free(p); p = np; } } stream_close_files(ctx, st); if (st->csv_name != NULL) free((void *)st->csv_name); if (st->output_name != NULL) free((void *)st->output_name); free(st); } static pcapreport_stream_t * stream_create(pcapreport_ctx_t * const ctx, const pcaprec_hdr_t * const pcap_pkt_hdr, const ethernet_packet_t * const epkt, uint32_t const dest_addr, const uint32_t dest_port) { int i; pcapreport_stream_t * st = calloc(1, sizeof(*st)); st->stream_no = ctx->stream_count++; st->output_dest_addr = dest_addr; st->output_dest_port = dest_port; st->vlan_count = epkt->vlan_count; for (i = 0; i < epkt->vlan_count; ++i) { st->vlans[i].vid = epkt->vlans[i].vid; // Maps are zero - will be filled in by merge_vlan_info } st->skew_discontinuity_threshold = ctx->opt_skew_discontinuity_threshold; st->force = ctx->keep_bad; // Even if we don't need sections it won't hurt to have one // Also generates output names if (section_create(ctx, st, pcap_pkt_hdr) == NULL) { stream_close(ctx, &st); return NULL; } return st; } static char * map_to_string(unsigned int n, const size_t blen, char * const buf) { int i = 0; char * p = buf; char * const eob = buf + blen; int first = TRUE; while (n != 0 && eob - p > 2) { if ((n & 1) != 0) { if (!first) *p++ = ','; p += snprintf(p, eob - p, "%d", i); first = FALSE; } n >>= 1; ++i; } return buf; } static void stream_analysis(const pcapreport_ctx_t * const ctx, const pcapreport_stream_t * const st) { uint32_t dest_addr = st->output_dest_addr; char pbuf[32]; if (ctx->verbose < 1 && st->seen_good == 0) return; fprint_msg("Stream %d: Dest:%s %u.%u.%u.%u:%u\n", st->stream_no, vlan_name(" VLAN:", st, sizeof(pbuf), pbuf), dest_addr >> 24, (dest_addr >> 16) & 0xff, (dest_addr >> 8) & 0xff, dest_addr & 0xff, st->output_dest_port); if (st->vlan_count != 0) { int i; for (i = 0; i < st->vlan_count; ++i) { const pcapreport_vlan_info_t * const vi = st->vlans + i; char pbuf1[64], pbuf2[64]; fprint_msg(" VLAN %d: cfi:[%s], pcp[%s]\n", vi->vid, map_to_string(vi->cfimap, sizeof(pbuf1), pbuf1), map_to_string(vi->pcpmap, sizeof(pbuf2), pbuf2)); } } if (st->seen_good == 0) { // Cut the rest of the stats short if they are meaningless fprint_msg(" No TS detected: Pkts=%u\n", st->seen_bad); } else { const pcapreport_section_t * tsect; fprint_msg(" Pkts: Good=%d, Dodgy=%d, Bad=%d, Overlength=%u\n", st->seen_good - st->seen_dodgy, st->seen_dodgy, st->seen_bad, st->pkts_overlength); if (st->rtp_info.n != 0) { const pcapreport_rtp_info_t * const ri = &st->rtp_info; fprint_msg(" RTP TS packets: %d, SSRC: %u%s\n", ri->n, ri->ssrc, ri->multiple_ssrc ? "..." : ""); } fprint_msg(" PCR PID: %d (%#x)%s\n", st->pcr_pid, st->pcr_pid, !st->multiple_pcr_pids ? "" : " ### Other PCR PIDs in stream - not tracked"); for (tsect = st->section_first; tsect != NULL; tsect = tsect->next) { uint64_t time_offset = ctx->time_start; int64_t time_len = tsect->time_last - tsect->time_first; // PCR duration int64_t time_len2 = tsect->time_final - tsect->time_start; // Stream duration int64_t pcr_len = pts_diff(tsect->pcr_last, tsect->pcr_start); int64_t drift = time_len - pcr_len; fprint_msg(" Section %d:\n", tsect->section_no); fprint_msg(" Pkts: %u->%u\n", tsect->pkt_start, tsect->pkt_final); fprint_msg(" TS Bytes: %llu (%llu bits/sec)\n", tsect->ts_byte_final - tsect->ts_byte_start, time_len2 == 0LL ? 0LL : (tsect->ts_byte_final - tsect->ts_byte_start) * 8ULL * 90000ULL / time_len2); fprint_msg(" Time (Total): %s->%s (%s)\n", fmtx_timestamp(tsect->time_start - time_offset, ctx->tfmt), fmtx_timestamp(tsect->time_final - time_offset, ctx->tfmt), fmtx_timestamp(time_len2, ctx->tfmt)); if (tsect->pcr_count == 0) { fprint_msg(" No PCRs seen\n"); } else { fprint_msg(" Time (PCRs): %s->%s (%s)\n", fmtx_timestamp(tsect->time_first - time_offset, ctx->tfmt), fmtx_timestamp(tsect->time_last - time_offset, ctx->tfmt), fmtx_timestamp(time_len, ctx->tfmt)); fprint_msg(" PCR[count=%u]: %s->%s (%s)\n", tsect->pcr_count, fmtx_timestamp(tsect->pcr_start, ctx->tfmt), fmtx_timestamp(tsect->pcr_last, ctx->tfmt), fmtx_timestamp(pcr_len, ctx->tfmt)); fprint_msg(" Drift: diff=%s; rate=%s/min; 1s per %llds%s\n", fmtx_timestamp(time_len - pcr_len, ctx->tfmt), fmtx_timestamp(time_len == 0 ? 0LL : drift * 60LL * 90000LL / time_len, ctx->tfmt), drift == 0 ? 0LL : time_len / drift, drift == 0 ? "" : drift < 0 ? " (fast)" : " (slow)"); fprint_msg(" Max jitter: %s; Skew min: %s, max: %s\n", fmtx_timestamp(tsect->jitter_max, ctx->tfmt), fmtx_timestamp(tsect->skew_min, ctx->tfmt), fmtx_timestamp(tsect->skew_max, ctx->tfmt)); } if (st->rtp_info.n != 0) { fprint_msg(" PCR/RTP skew: min=%s max=%s (diff=%s)\n", fmtx_timestamp(tsect->rtp_skew_min, ctx->tfmt), fmtx_timestamp(tsect->rtp_skew_max, ctx->tfmt), fmtx_timestamp(tsect->rtp_skew_max - tsect->rtp_skew_min, ctx->tfmt)); } } } fprint_msg("\n"); } unsigned int stream_hash(uint32_t const dest_addr, const uint32_t dest_port) { uint32_t x = dest_addr ^ dest_port; x ^= x >> 16; return (x ^ (x >> 8)) & 0xff; } static int stream_vlan_match(const pcapreport_stream_t * const st, const ethernet_packet_t * const epkt) { int i; if (epkt->vlan_count != st->vlan_count) return FALSE; for (i = 0; i < epkt->vlan_count; ++i) { if (epkt->vlans[i].vid != st->vlans[i].vid) return FALSE; } return TRUE; } pcapreport_stream_t * stream_find(pcapreport_ctx_t * const ctx, const pcaprec_hdr_t * const pcap_pkt_hdr, const ethernet_packet_t * const epkt, uint32_t const dest_addr, const uint32_t dest_port) { const unsigned int h = stream_hash(dest_addr, dest_port); pcapreport_stream_t ** pst = ctx->stream_hash + h; pcapreport_stream_t * st; while ((st = *pst) != NULL) { if (st->output_dest_addr == dest_addr && st->output_dest_port == dest_port && stream_vlan_match(st, epkt)) { return st; } pst = &st->hash_next; } if ((st = stream_create(ctx, pcap_pkt_hdr, epkt, dest_addr, dest_port)) == NULL) return NULL; *pst = st; return st; } static int stream_sort_fn(const void *va, const void * vb) { const pcapreport_stream_t * const * const pa = va; const pcapreport_stream_t * const * const pb = vb; return (*pa)->stream_no - (*pb)->stream_no; } static int ip_reassemble(pcapreport_reassembly_t * const reas, const ipv4_header_t * const ip, byte * const in_data, byte ** const out_pdata, uint32_t * const out_plen) { uint32_t frag_len = ip->length - ip->hdr_length * 4; uint32_t frag_offset = ip->frag_offset * 8; // bytes int frag_final = (ip->flags & 1) == 0; // Discard unless we succeed *out_pdata = (void *)NULL; *out_plen = 0; if (frag_final && frag_offset == 0) { // Normal case - no fragmentation *out_pdata = in_data; *out_plen = frag_len; return 0; } if ((frag_len & 7) != 0 && !frag_final) { // Only final fragment may have length that is not a multiple of 8 fprint_err("### Non-final fragment with bad length: %d\n", frag_len); return -1; } if (frag_len + frag_offset >= 0x10000) { // I can't find this explicitly prohibited in RFC791 but it can't be good // and the limit should probably be a little less if we were being pedantic fprint_err("### Fragment end >= 64k: %d+%d\n", frag_offset, frag_len); return -1; } // Very limited reassembly { pcapreport_fragment_t * frag = &reas->frag; if (frag->in_use && frag->ident != ip->ident) { fprint_err("### Multi-packet fragment reassembly NIF - previous packet discarded\n"); frag->in_use = 0; } // If previously idle then reset stuff if (!frag->in_use) { frag->in_use = 1; frag->current_len = 0; frag->ident = ip->ident; } if (frag->current_len != frag_offset) { fprint_err("### Reordering fragment reassembly NIF - packet discarded\n"); frag->in_use = 0; return -1; } frag->current_len = frag_offset + frag_len; memcpy(frag->pkt + ip->frag_offset * 8, in_data, frag_len); if (!frag_final) return 1; *out_pdata = frag->pkt; *out_plen = frag->current_len; frag->in_use = 0; return 0; } } static int ip_reassembly_init(pcapreport_reassembly_t * const reas) { memset(reas, 0, sizeof(*reas)); return 0; } static void print_usage() { print_msg( "Usage: pcapreport [switches] \n" "\n" ); REPORT_VERSION("pcapreport"); print_msg( "\n" "Report on a pcap capture file.\n" "\n" " -h This help\n" " -h detail More detail on what some terms used by pcapreport mean\n" " --name \n" " -n Set the default base name for output files; by default\n" " this will be the input name without any .pcap suffix\n" " -x, --extract Extract TS(s) to files of the default name\n" " -c, --csvgen Create a .csv file for each stream containing timing info\n" " -output \n" " -o , Dump selected UDP payloads to output file(s)\n" " Uses given filename if : specified,\n" " otherwise appends _ to filename per TS\n" " Is much the same as -x -n \n" " -a Analyse. Produces summary info on every TS in the pcap\n" " -d :\n" " -d Select data with the given destination IP and port.\n" " If the is not specified, it defaults to 0\n" " (see below).\n" " -g, --good-ts-only Only extract/analyse packets that seem entirely good.\n" " By default there is a bit of slack in determining if a\n" " packet is good and some dodgy packets are let through\n" " This switch ensures that all packets pass simple testing\n" " -keep-bad Extract all packets including bad ones. Is implied if\n" " an ip & port filter is set. Overriden by --good-ts-only.\n" " -tfmt 32|90|ms|hms Set time format in report [default = 90kHz units]\n" " -dump-data, -D Dump any data in the input file to stdout.\n" " -extra-dump, -e Dump only data which isn't being sent to the -o file.\n" " -times, -t Report continuously on PCR vs PCAP timing for the\n" " destination specified in -d.\n" " -verbose, -v Output metadata about every packet.\n" " -skew-discontinuity-threshold \n" " -skew Gives the skew discontinuity threshold in 90kHz units.\n" " A value of 0 disables this. [default = 6*90000]\n" " -split-section Split extracted streams into multiple files on section\n" " (discontinutity) boundries\n" "\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" "\n" "Specifying 0.0.0.0 for destination IP will capture all hosts, specifying 0\n" "as a destination port will capture all ports on the destination host.\n" "\n" "Network packet numbers start at 1 (like wireshark)\n" "TS packet numbers start at 0.\n" "\n" "Positive skew means that we received too low a PCR for this timestamp.\n" "\n" ); } static char manpage[] = "Times (packet and PCR)\n" "----------------------\n" "\n" "The times associated with packets and PCR are held internally in 90kHz units\n" "and are displayed in those units by default\n" "\n" "Stream\n" "------\n" "\n" "A set of packets to the same IP & Port. TS streams are detected by looking\n" "for 0x47s at appropriate places in the packets\n" "\n" "Section\n" "-------\n" "A part of a stream which appears to have a continuous TS embedded in it. If\n" "the PCR jumps then a new section should be started (though this will not\n" "generate a separate .ts file if the extraction option is in effect, nor will\n" "it generate a new .csv file.)\n" "\n" "As it stands pcapreport will only report on a single PCR pid within a TS. If\n" "multiple pids with PCRs are detected then this will be reported but the other\n" "PCRs will be ignored\n" "\n" "Skew\n" "----\n" "\n" "This is the difference between the time in the pcap for a UDP packet and any\n" "PCR found in the TS contained within that packet. The accuracy of this figure\n" "obviously depends on how good the clock was in the capture process. Skew is\n" "arbitrarily set to zero at the start of a section. A skew of >6s is assumed\n" "to be a discontinuity and will start a new section.\n" "\n" "Drift\n" "-----\n" "\n" "This is skew over time and (assuming that the playout process is good)\n" "represents the difference in speed between the transmitters clock and the\n" "receivers clock. The algorithm for determining this isn't very sophisticated\n" "so if you have a large maximum jitter or a short sample this should be taken\n" "with a pinch of salt. Beware also that PC clocks (like the one in the m/c\n" "doing the tcpdump) are not always amongst the most stable or accurate; however\n" "they should be good enough to detect gross errors\n" "\n" "Jitter\n" "------\n" "\n" "This is measured as the difference between the maximum and minimum skews over\n" "a 10sec (max 1024 samples) period. This should be long enough to capture a\n" "good baseline but short enough that drift has a negligible effect\n" "\n" "Max Jitter\n" "----------\n" "\n" "The maximum value of jitter (see above) found in a section\n" ""; const char *onechararg[26] = { "analyse", // a "", // b "csvgen", // c "destip", // d "", // e "", // f "good-ts-only", // g "help", // h "", // i "", // j "", // k "", // l "", // m "name", // n "output", // o "", // p "", // q "", // r "", // s "times", // t "", // u "verbose", // v "", // w "extract", // x "", // y "" // z }; int main(int argc, char **argv) { int err = 0; int ii = 1; pcapreport_ctx_t sctx = {0}; pcapreport_ctx_t * const ctx = &sctx; ctx->opt_skew_discontinuity_threshold = SKEW_DISCONTINUITY_THRESHOLD; ctx->tfmt = FMTX_TS_DISPLAY_90kHz_RAW; ctx->rtp_raw_wanted[96] = 1; ip_reassembly_init(&ctx->reassembly_env); if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { // remove double dashes const char c = argv[ii][1]; const char * const arg = c >= 'a' && c <= 'z' && argv[ii][2] == 0 ? onechararg[c - 'a'] : argv[ii][1] == '-' ? argv[ii] + 2 : argv[ii] + 1; if (strcmp("help", arg) == 0) { if (ii + 1 < argc && strcmp("detail", argv[ii + 1]) == 0) { fwrite(manpage, sizeof(manpage), 1, stdout); exit(0); } print_usage(); return 0; } else if (!strcmp("err",arg)) { CHECKARG("pcapreport",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### pcapreport: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("output", arg)) { CHECKARG("pcapreport",ii); ctx->output_name_base = argv[++ii]; ctx->extract_data = TRUE; } else if (!strcmp("times", arg)) { ++ctx->time_report; } else if (!strcmp("analyse", arg)) { ctx->analyse = TRUE; } else if (!strcmp("verbose", arg)) { ++ctx->verbose; } else if (!strcmp("destip", arg)) { char *hostname; int port = 0; CHECKARG("pcapreport",ii); err = host_value("pcapreport", argv[ii], argv[ii+1], &hostname, &port); if (err) return 1; ++ii; ctx->filter_dest_port = port; if (ipv4_string_to_addr(&ctx->filter_dest_addr, hostname)) { fprint_err( "### pcapreport: '%s' is not a host IP address (names are not allowed!)\n", hostname); return 1; } } else if (!strcmp("dump-data", arg) || !strcmp("D", arg)) { ++ctx->dump_data; } else if (!strcmp("extra-dump", arg) || !strcmp("E", arg)) { ++ctx->dump_extra; } else if (!strcmp("skew-discontinuity-threshold", arg) || !strcmp("skew", arg)) { int val; CHECKARG("pcapreport",ii); err = int_value("pcapreport", argv[ii], argv[ii+1], TRUE, 0, &val); if (err) return 1; ctx->opt_skew_discontinuity_threshold = val; ++ii; } else if (strcmp("name", arg) == 0) { CHECKARG("pcapreport",ii); ctx->base_name = strdup(argv[++ii]); // So we know it is always malloced } else if (strcmp("extract", arg) == 0) { ctx->extract = TRUE; } else if (strcmp("csvgen", arg) == 0) { ctx->csv_gen = TRUE; } else if (strcmp("good-ts-only", arg) == 0) { ctx->good_ts_only = TRUE; } else if (strcmp("keep-bad", arg) == 0) { ctx->keep_bad = TRUE; } else if (strcmp("split-section", arg) == 0) { ctx->file_split_section = TRUE; } else if (strcmp("tfmt", arg) == 0) { int tfmt; CHECKARG("pcapreport",ii); if ((tfmt = fmtx_str_to_timestamp_flags(argv[ii + 1])) < 0) { fprint_err("### Bad timeformat: %s\n", argv[ii + 1]); exit(1); } ctx->tfmt = tfmt; ++ii; } else { fprint_err( "### pcapreport: " "Unrecognised command line switch '%s'\n", argv[ii]); return 1; } } else { if (ctx->had_input_name) { fprint_err( "### pcapreport: Unexpected '%s'\n", argv[ii]); return 1; } else { ctx->input_name = argv[ii]; ctx->had_input_name = TRUE; } } ++ii; } if (!ctx->had_input_name) { print_err("### pcapreport: No input file specified\n"); return 1; } // If the dest:port is fully specified then avoid guesswork if (ctx->filter_dest_addr != 0 && ctx->filter_dest_port != 0) ctx->keep_bad = TRUE; // Good only overrides keep bad if (ctx->good_ts_only) ctx->keep_bad = FALSE; if (ctx->base_name == NULL) { // If we have no default name then use the input name as a base after // stripping off any likely pcap extension static const char * const strip_exts[] = { ".cap", ".pcap", ".pcapng" }; const char * const input_name = ctx->input_name == NULL ? "pcap" : ctx->input_name; char * const buf = strdup(ctx->input_name); const size_t len = strlen(input_name); int i; for (i = 0; i != sizeof(strip_exts)/sizeof(strip_exts[0]); ++i) { const size_t extlen = strlen(strip_exts[i]); if (len > extlen && strcmp(strip_exts[i], buf + len - extlen) == 0) { buf[len - extlen] = 0; break; } } ctx->base_name = buf; } fprint_msg("%s\n",ctx->input_name); err = pcap_open(&ctx->pcreader, &ctx->pcap_hdr, ctx->input_name); if (err) { fprint_err("### pcapreport: Unable to open input file %s for reading " "PCAP (code %d)\n", ctx->had_input_name?ctx->input_name:"", err); // Just an error code isn't much use - let's look at the source // and report something more helpful... fprint_err(" %s\n", (err==-1?"Unable to open file": err==-2?"Unable to allocate PCAP reader datastructure": err==-4?"Unable to read PCAP header - is it a PCAP file?": "")); return 1; } fprint_msg("Capture made by version %u.%u local_tz_correction " "%d sigfigs %u snaplen %d network %u\n", ctx->pcap_hdr.version_major, ctx->pcap_hdr.version_minor, ctx->pcap_hdr.thiszone, ctx->pcap_hdr.sigfigs, ctx->pcap_hdr.snaplen, ctx->pcap_hdr.network); if (ctx->pcap_hdr.snaplen < 65535) { fprint_err("### pcapreport: WARNING snaplen is %d, not >= 65535 - " "not all data may have been captured.\n", ctx->pcap_hdr.snaplen); } { int done = 0; while (!done) { pcaprec_hdr_t rec_hdr; byte *data = NULL; uint32_t len = 0; int sent_to_output = 0; err = pcap_read_next(ctx->pcreader, &rec_hdr, &data, &len); switch (err) { case 0: // EOF. ++done; break; case 1: // Got a packet. { byte *allocated = data; // Wireshark numbers packets from 1 so we shall do the same if (ctx->pkt_counter++ == 0) { // Note time of 1st packet ctx->time_usec = rec_hdr.ts_usec; ctx->time_sec = rec_hdr.ts_sec; ctx->time_start = pkt_time(&rec_hdr); } if (ctx->verbose) { fprint_msg("pkt: Time = %d.%d orig_len = %d \n", rec_hdr.ts_sec, rec_hdr.ts_usec, rec_hdr.orig_len); } if (!(ctx->pcap_hdr.network == PCAP_NETWORK_TYPE_ETHERNET)) { goto dump_out; } { ethernet_packet_t epkt; uint32_t out_st, out_len; int rv; ipv4_header_t ipv4_hdr; ipv4_udp_header_t udp_hdr; rv = ethernet_packet_from_pcap(&rec_hdr, data, len, &epkt, &out_st, &out_len); if (rv) { goto dump_out; } if (ctx->verbose) { fprint_msg("++ 802.11: src %02x:%02x:%02x:%02x:%02x:%02x " " dst %02x:%02x:%02x:%02x:%02x:%02x " "typeorlen 0x%04x\n", epkt.src_addr[0], epkt.src_addr[1], epkt.src_addr[2], epkt.src_addr[3], epkt.src_addr[4], epkt.src_addr[5], epkt.dst_addr[0], epkt.dst_addr[1], epkt.dst_addr[2], epkt.dst_addr[3], epkt.dst_addr[4], epkt.dst_addr[5], epkt.typeorlen); } data = &data[out_st]; len = out_len; // Is it IP? if (epkt.typeorlen != 0x800) { goto dump_out; } rv = ipv4_from_payload(data, len, &ipv4_hdr, &out_st, &out_len); if (rv) { goto dump_out; } if (ctx->verbose) { fprint_msg("++ IPv4: src = %s", ipv4_addr_to_string(ipv4_hdr.src_addr)); fprint_msg(" dest = %s \n", ipv4_addr_to_string(ipv4_hdr.dest_addr)); fprint_msg("++ IPv4: version = 0x%x hdr_length = 0x%x" " serv_type = 0x%08x length = 0x%04x\n" "++ IPv4: ident = 0x%04x flags = 0x%02x" " frag_offset = 0x%04x ttl = %d\n" "++ IPv4: proto = %d csum = 0x%04x\n", ipv4_hdr.version, ipv4_hdr.hdr_length, ipv4_hdr.serv_type, ipv4_hdr.length, ipv4_hdr.ident, ipv4_hdr.flags, ipv4_hdr.frag_offset, ipv4_hdr.ttl, ipv4_hdr.proto, ipv4_hdr.csum); } data = &data[out_st]; len = out_len; if (ip_reassemble(&ctx->reassembly_env, &ipv4_hdr, data, &data, &len) != 0) { goto dump_out; } if (!(IPV4_HDR_IS_UDP(&ipv4_hdr))) { goto dump_out; } rv = ipv4_udp_from_payload(data, len, &udp_hdr, &out_st, &out_len); if (rv) { goto dump_out; } if (ctx->verbose) { fprint_msg("++ udp: src port = %d " "dest port = %d len = %d \n", udp_hdr.source_port, udp_hdr.dest_port, udp_hdr.length); } data = &data[out_st]; len = out_len; if ( (ctx->filter_dest_addr == 0 || (ipv4_hdr.dest_addr == ctx->filter_dest_addr)) && (ctx->filter_dest_port == 0 || (udp_hdr.dest_port == ctx->filter_dest_port))) { pcapreport_stream_t * const st = stream_find(ctx, &rec_hdr, &epkt, ipv4_hdr.dest_addr, udp_hdr.dest_port); rtp_header_t rtp_hdr; stream_merge_vlan_info(st, &epkt); if (stream_rtp_check(ctx, st, data, len, &rtp_hdr)) { if (ctx->extract && rtp_hdr.is_rtp_raw) { stream_gen_names(ctx, st, &rtp_hdr); write_rtp_raw_packet(ctx, st, data, len); } data += rtp_hdr.header_len; len -= rtp_hdr.header_len + rtp_hdr.pad_len; } if (stream_ts_check(ctx, st, data, len)) { ++sent_to_output; if (ctx->time_report || ctx->analyse || ctx->csv_gen || (ctx->extract && ctx->file_split_section)) { rv = digest_times(ctx, st, &rec_hdr, &epkt, &ipv4_hdr, &udp_hdr, &rtp_hdr, data, len); if (rv) { return rv; } } if (ctx->extract) { rv = write_out_packet(ctx, st, data, len); if (rv) { return rv; } } } } } // Adjust dump_out: if (ctx->dump_data || (ctx->dump_extra && !sent_to_output)) { print_data(TRUE, "data", data, len, len); } free(allocated); allocated = data = NULL; } break; default: // Some other error. fprint_err( "### pcapreport: Can't read packet %d - code %d\n", ctx->pkt_counter, err); ++done; break; } } } pcap_close(&ctx->pcreader); // Analyse data if requested if (ctx->analyse) { // Spit out pcap part of the report // const struct tm * const t = gmtime(&ctx->time_sec); fprint_msg("Pcap start time: %llu (%d-%02d-%02d %d:%02d:%02d.%06d)\n", ctx->time_start, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, ctx->time_usec); fprint_msg("Pcap pkts: %u\n", ctx->pkt_counter); fprint_msg("\n"); // Spit out the per stream info if (ctx->stream_count != 0) { unsigned int i; unsigned int j = 0; pcapreport_stream_t ** const streams = malloc(sizeof(pcapreport_stream_t *) * ctx->stream_count); // Add to array for sorting for (i = 0; i != 256; ++i) { pcapreport_stream_t * st = ctx->stream_hash[i]; while (st != NULL) { streams[j++] = st; st = st->hash_next; } } // Sort into stream_no order qsort(streams, ctx->stream_count, sizeof(pcapreport_stream_t *), stream_sort_fn); // Display for (i = 0; i != ctx->stream_count; ++i) stream_analysis(ctx, streams[i]); free(streams); } } // Kill it { unsigned int i; for (i = 0; i != 256; ++i) { while (ctx->stream_hash[i] != NULL) stream_close(ctx, ctx->stream_hash + i); } } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pes.c000066400000000000000000003404021261471605300156260ustar00rootroot00000000000000/* * PES reading facilities * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "ts_fns.h" #include "ps_fns.h" #include "es_fns.h" #include "pes_fns.h" #include "pidint_fns.h" #include "h262_fns.h" #include "tswrite_fns.h" #include "printing_fns.h" #include "misc_fns.h" //#define DEBUG #define DEBUG_READ_PACKETS 0 #define DEBUG_PES_ASSEMBLY 0 #define DEBUG_PROGRAM_INFO 1 #define SHOW_PROGRAM_INFO 0 #define ALLOW_OVERLONG_PACKETS 1 // PES packet stream ids // See H.222.0 Table 2-17 and Table 2-18 // These are specifically the stream ids used to decide if a PES packet // contains header data, filler bytes or what #define STREAM_ID_PROGRAM_STREAM_MAP 0xbc #define STREAM_ID_PRIVATE_STREAM_1 0xbd #define STREAM_ID_PADDING_STREAM 0xbe #define STREAM_ID_PRIVATE_STREAM_2 0xbf #define STREAM_ID_ECM_STREAM 0xf0 #define STREAM_ID_EMM_STREAM 0xf1 #define STREAM_ID_DSMCC_STREAM 0xf2 #define STREAM_ID_13522_STREAM 0xf3 #define STREAM_ID_H222_A_STREAM 0xf4 #define STREAM_ID_H222_B_STREAM 0xf5 #define STREAM_ID_H222_C_STREAM 0xf6 #define STREAM_ID_H222_D_STREAM 0xf7 #define STREAM_ID_H222_E_STREAM 0xf8 #define STREAM_ID_ANCILLARY_STREAM 0xf9 #define STREAM_ID_PROGRAM_STREAM_DIRECTORY 0xff // ============================================================ // PES packet data // ============================================================ /* * Build a new PES packet datastructure * * Returns 0 if all goes well, 1 if something goes wrong */ static int build_PES_packet_data(PES_packet_data_p *data) { PES_packet_data_p new = malloc(SIZEOF_PES_PACKET_DATA); if (new == NULL) { print_err("### Unable to allocate PES packet datastructure\n"); return 1; } new->data = NULL; new->data_len = 0; new->es_data_len = 0; new->length = 0; new->posn = 0; new->is_video = TRUE; // a guess new->data_alignment_indicator = FALSE; // another new->has_PTS = FALSE; // assumed until told otherwise *data = new; return 0; } /* * Add some data to a PES packet datastructure * * - `data` is the PES packet datastructure concerned * - `bytes` is the data to add * - `bytes_len` is how much data there is * * Returns 0 if all goes well, 1 if something goes wrong */ static inline int extend_PES_packet_data(PES_packet_data_p data, byte bytes[], int bytes_len) { if (data->data == NULL) { data->data = malloc(bytes_len); if (data->data == NULL) { print_err("### Unable to extend PES packet data array\n"); return 1; } memcpy(data->data,bytes,bytes_len); data->data_len = bytes_len; } else { data->data = realloc(data->data,data->data_len + bytes_len); if (data->data == NULL) { print_err("### Unable to extend PES packet data array\n"); return 1; } memcpy(&(data->data[data->data_len]),bytes,bytes_len); data->data_len = data->data_len + bytes_len; } return 0; } /* * Build a dummy PES packet datastructure. * * - `data` is the "new" dummy PES packet. Do not free this, * as it is remembered statically inside the function. * - `data_len` is the required (total) size of the dummy PES packet * * Returns 0 if all goes well, 1 if something goes wrong */ static inline int build_dummy_PES_packet_data(PES_packet_data_p *data, int data_len) { int err; static PES_packet_data_p local_data = NULL; if (local_data == NULL) { err = build_PES_packet_data(&local_data); if (err) { print_err("### Error building dummy PES packet\n"); return 1; } local_data->is_video = FALSE; } if (local_data->data == NULL) { local_data->data = malloc(data_len); if (local_data->data == NULL) { print_err("### Unable to extend dummy PES packet data array\n"); return 1; } memset(local_data->data,0xFF,data_len); } else if (data_len > local_data->data_len) { local_data->data = realloc(local_data->data,data_len); if (local_data->data == NULL) { print_err("### Unable to extend dummy PES packet data array\n"); return 1; } memset(local_data->data,0xFF,data_len); } if (data_len != local_data->data_len) { int PES_packet_len = data_len - 6; // Set up the data in the PES packet local_data->data[0] = 0x00; local_data->data[1] = 0x00; local_data->data[2] = 0x01; // end of the packet_start_code_prefix local_data->data[3] = STREAM_ID_PADDING_STREAM; if (PES_packet_len > 0xFFFF) { local_data->data[4] = 0; local_data->data[5] = 0; } else { local_data->data[4] = (byte) ((PES_packet_len & 0xFF00) >> 8); local_data->data[5] = (byte) ((PES_packet_len & 0x00FF)); } local_data->data_len = data_len; } *data = local_data; return 0; } /* * Free a PES packet datastructure * * - `data` is the PES packet datastructure, which will be freed, * and returned as NULL. */ extern void free_PES_packet_data(PES_packet_data_p *data) { if ((*data) == NULL) return; if ((*data)->data != NULL) { free((*data)->data); (*data)->data = NULL; } (*data)->data_len = 0; (*data)->length = 0; free(*data); *data = NULL; return; } // ============================================================ // Transport Stream support - PID -> PES data // (datastructure support based on that for pidint lists in ts.c) // ============================================================ /* * Initialise a new PID/PES data datastructure. */ static int init_peslist(peslist_p list) { int ii; list->length = 0; list->size = PESLIST_START_SIZE; list->data = malloc(SIZEOF_PES_PACKET_DATA*PESLIST_START_SIZE); if (list->data == NULL) { print_err("### Unable to allocate PES array in PID/PES data array"); return 1; } list->pid = malloc(sizeof(uint32_t)*PESLIST_START_SIZE); if (list->pid == NULL) { free(list->data); print_err("### Unable to allocate PID array in PID/PES data array\n"); return 1; } // Just in case... for (ii=0; iisize; ii++) list->data[ii] = NULL; return 0; } /* * Build a new PID/PES data datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int build_peslist(peslist_p *list) { peslist_p new = malloc(SIZEOF_PESLIST); if (new == NULL) { print_err("### Unable to allocate PID/PES data array\n"); return 1; } if (init_peslist(new)) return 1; *list = new; return 0; } /* * Tidy up and free a PID/PES data datastructure after we've finished with it * * Clears the datastructure, frees it and returns `list` as NULL. * * Does nothing if `list` is already NULL. */ static void free_peslist(peslist_p *peslist) { peslist_p list = *peslist; if (list == NULL) return; if (list->data != NULL) { int ii; for (ii=0; iilength; ii++) { if (list->data[ii] != NULL) free_PES_packet_data(&list->data[ii]); } free(list->data); list->data = NULL; } if (list->pid != NULL) { free(list->pid); list->pid = NULL; } list->length = 0; list->size = 0; free(list); *peslist = NULL; } /* * Lookup a PID to find its index in a PID/PES data array. * * Note that if `list` is NULL, then -1 will be returned. * * Returns its index (0 or more) if the PID is in the list, -1 if it is not. */ static inline int pid_index_in_peslist(peslist_p list, uint32_t pid) { int ii; if (list == NULL) return -1; for (ii = 0; ii < list->length; ii++) { if (list->pid[ii] == pid) return ii; } return -1; } /* * Lookup a PID to see if it is in a PID/PES data array. * * Note that if `list` is NULL, then FALSE will be returned. * * Returns TRUE if the PID is in the list, FALSE if it is not. */ static inline int pid_in_peslist(peslist_p list, uint32_t pid) { return pid_index_in_peslist(list,pid) != -1; } /* * Start a new PES packet for a PID. * * Creates a new entry for the given PID, and returns the corresponding new * PES packet. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int start_packet_in_peslist(PES_reader_p reader, uint32_t pid, int is_video, PES_packet_data_p *data) { int err; int ii; peslist_p list = reader->packets; if (list == NULL) { print_err("### Unable to append to NULL PID/PES data array\n"); return 1; } err = build_PES_packet_data(data); if (err) { print_err("### Unable to build new PES packet datastructure" " for PID/PES data array\n"); return 1; } (*data)->is_video = is_video; for (ii=0; iilength; ii++) { if (list->pid[ii] == pid) { // There is already an entry for this PID - does it have data? if (list->data[ii] != NULL) { PES_packet_data_p packet = list->data[ii]; if (reader->give_warning) fprint_err("!!! PID %04x (%d) already has an unfinished PES packet" " associated with it\n %d byte%s of %d bytes were already" " read - ignoring them\n",pid,pid,packet->data_len, (packet->data_len==1?"":"s"),packet->length); free_PES_packet_data(&(list->data[ii])); } list->data[ii] = *data; return 0; } } // Otherwise, we need to add a new entry to the list if (list->length == list->size) { int newsize = list->size + PESLIST_INCREMENT; list->data = realloc(list->data,newsize*SIZEOF_PES_PACKET_DATA); if (list->data == NULL) { print_err("### Unable to extend PID/PES data array\n"); free_PES_packet_data(data); return 1; } list->pid = realloc(list->pid,newsize*sizeof(uint32_t)); if (list->pid == NULL) { print_err("### Unable to extend PID/PES data array\n"); free_PES_packet_data(data); return 1; } list->size = newsize; } list->pid[list->length] = pid; list->data[list->length] = *data; list->length++; return 0; } /* * Find the PES packet for a PID. * * NB: returns `data` as NULL if the data for the PID *is* NULL. * * Returns 0 if it succeeds, 1 if some error occurs. */ static inline int find_packet_in_peslist(peslist_p list, uint32_t pid, PES_packet_data_p *data) { int index = pid_index_in_peslist(list,pid); if (index == -1) return 1; *data = list->data[index]; return 0; } /* * Clear the PES packet for a PID. * * Leaves the entry in the PID/PES array (with NULL data pointer) around for * reuse. * * Returns 0 if it succeeds, 1 if some error occurs. */ static int clear_packet_in_peslist(peslist_p list, uint32_t pid) { int index; if (list == NULL) { print_msg("Unable to clear PES packet in NULL PID/PES data array\n"); return 1; } index = pid_index_in_peslist(list,pid); if (index == -1) { fprint_err("### Unable to find PID %04x (%x) in PID/PES data array," " so cannot clear its data\n",pid,pid); return 1; } list->data[index] = NULL; return 0; } // ============================================================ // Reading the Program Stream // ============================================================ /* * Return the next PES packet from the input PS file * * - `reader` is a PES reader context * - `packet_data` is the packet data (NULL if EOF is read) * * Returns 0 if all goes well, EOF if end of file is read, and 1 if * something goes wrong. */ static int read_next_PES_packet_from_PS(PES_reader_p reader, PES_packet_data_p *packet_data) { // Read PS packets // If a packet is a PES packet, return it for (;;) { int err; byte stream_id; // The packet's stream id int keep = FALSE; // Keep this packet? int is_video = FALSE; struct PS_packet packet = {0}; struct PS_pack_header header = {0}; err = read_PS_packet_start(reader->psreader,FALSE,&reader->posn, &stream_id); if (err == EOF) { *packet_data = NULL; return EOF; } else if (err) return 1; // If it's the pack header, read it and ignore it if (stream_id == 0xba) { err = read_PS_pack_header_body(reader->psreader,&header); if (err == EOF) { fprint_err("!!! Unexpected EOF - partial PS packet at " OFFSET_T_FORMAT " ignored\n",reader->posn); *packet_data = NULL; return EOF; } else if (err) { fprint_err("### Error reading data for pack header starting at " OFFSET_T_FORMAT "\n",reader->posn); return 1; } continue; } err = read_PS_packet_body(reader->psreader,stream_id,&packet); if (err == EOF) { fprint_err("!!! Unexpected EOF - partial PS packet at " OFFSET_T_FORMAT " ignored\n",reader->posn); *packet_data = NULL; return EOF; } else if (err) { fprint_err("### Error reading PS packet starting at " OFFSET_T_FORMAT "\n",reader->posn); return 1; } // We have to decide whether to discard this data because it is not // a "PES" packet. // First, we know we can discard things that we are sure are part of the // PS infrastructure. Note that we don't need to check for 0xba (pack // header) because we already did that above, and we shouldn't have to // check for 0xb9 (MPEG_program_end_code), because that should already // have been interpreted as EOF by read_PS_packet_start(). if (stream_id == 0xbb || // PS system header stream_id == 0xbc || // PS map stream_id == 0x01) // PS directory { /* pass */; } else if (stream_id == PRIVATE1_AUDIO_STREAM_ID) { // It's private stream 1, traditionally used for Dolby (AC-3) audio if (reader->video_only) keep = FALSE; else if (reader->audio_stream_id == stream_id) { keep = TRUE; is_video = FALSE; } } else if ((stream_id >= 0xc0) && (stream_id <= 0xdf)) { // It's a non-Dolby audio stream if (reader->video_only) keep = FALSE; else if (reader->audio_stream_id == stream_id) { keep = TRUE; is_video = FALSE; } else if (reader->audio_stream_id == 0) { // Aha - we're looking for an audio stream to use, and this is it reader->audio_stream_id = stream_id; keep = TRUE; is_video = FALSE; if (reader->give_info) fprint_msg("Selecting audio stream number %d\n",stream_id & 0x1F); } } else if (stream_id >= 0xe0 && stream_id <= 0xef) { // It's a video stream. We're assuming we only get one video // stream, so this is a "keeper" regardless keep = TRUE; is_video = TRUE; } if (keep) { err = build_PES_packet_data(packet_data); if (err) return 1; // We needn't copy the bytes from one "packet" to another, // it's easier to just transfer the array, if we're careful (*packet_data)->data = packet.data; (*packet_data)->data_len = packet.data_len; (*packet_data)->length = packet.data_len; (*packet_data)->posn = reader->posn; (*packet_data)->is_video = is_video; // So the data array is no longer "present" in the orignal "packet" packet.data = NULL; packet.data_len = 0; break; } clear_PS_packet(&packet); } return 0; } // ============================================================ // Reading the Transport Stream // ============================================================ /* * Look in the current program map to decide on the video and audio PIDs * * This is only expected to be called if the PMT has changed. * * TODO: The detection of different types of audio is not really strong enough * TODO: Take account of descriptors in deciding what things are, as well */ static void decide_pids(PES_reader_p reader) { pmt_p pmt = reader->program_map; int ii; int had_video = FALSE; int had_audio = FALSE; if (pmt == NULL) return; // Since we're not expecting our datastructure to be very big, // or this function to be called very often, we can look at audio // and video separately. for (ii = 0; ii < pmt->num_streams; ii++) { if (IS_VIDEO_STREAM_TYPE(pmt->streams[ii].stream_type)) { if (had_video) { if (reader->give_warning) fprint_err("!!! Multiple video streams in TS program %d, PMT" " at " OFFSET_T_FORMAT " - using PID %04x\n", reader->program_number,reader->posn,reader->video_pid); break; } else if (reader->video_pid == 0) { reader->video_pid = pmt->streams[ii].elementary_PID; switch (pmt->streams[ii].stream_type) { case AVC_VIDEO_STREAM_TYPE: reader->is_h264 = TRUE; reader->video_type = VIDEO_H264; break; case MPEG2_VIDEO_STREAM_TYPE: case MPEG1_VIDEO_STREAM_TYPE: // well, more-or-less reader->is_h264 = FALSE; reader->video_type = VIDEO_H262; break; case AVS_VIDEO_STREAM_TYPE: reader->is_h264 = FALSE; reader->video_type = VIDEO_AVS; break; default: reader->is_h264 = FALSE; reader->video_type = VIDEO_UNKNOWN; break; } if (reader->give_info) fprint_msg(" Chose video PID %04x\n",reader->video_pid); } else if (pmt->streams[ii].elementary_PID != reader->video_pid) { if (reader->give_warning) fprint_err("!!! Video streams altered in TS program %d, PMT" " at " OFFSET_T_FORMAT " - still using PID %04x\n", reader->program_number,reader->posn,reader->video_pid); break; } had_video = TRUE; } } if (reader->video_only) { if (reader->give_info) print_msg(" Not interested in any audio streams\n"); return; } for (ii = 0; ii < pmt->num_streams; ii++) { // Note that the audio detection will accept either DVB or ADTS Dolby // (AC-3) stream types if (IS_AUDIO_STREAM_TYPE(pmt->streams[ii].stream_type)) { if (had_audio) { if (reader->give_warning) fprint_err("!!! Multiple audio streams in TS program %d, PMT" " at " OFFSET_T_FORMAT " - using PID %04x\n", reader->program_number,reader->posn,reader->audio_pid); break; } else if (reader->audio_pid == 0) { reader->audio_pid = pmt->streams[ii].elementary_PID; if (reader->give_info) fprint_msg(" Chose audio PID %04x\n",reader->audio_pid); if (IS_DOLBY_STREAM_TYPE(pmt->streams[ii].stream_type)) { // Remember what stream type this Dolby data is using // - we'll assume that this doesn't change under our feet, // and thus we don't need to report if it changes reader->dolby_stream_type = pmt->streams[ii].stream_type; // If we're not overriding the output stream type, use it as is if (!reader->override_dolby_stream_type) reader->output_dolby_stream_type = reader->dolby_stream_type; } } else if (pmt->streams[ii].elementary_PID != reader->audio_pid) { if (reader->give_warning) fprint_err("!!! Audio streams altered in TS program %d, PMT" " at " OFFSET_T_FORMAT " - still using PID %04x\n", reader->program_number,reader->posn,reader->audio_pid); break; } had_audio = TRUE; } } if (!reader->override_program_data) { // Our output program data is to be the same as our input reader->output_video_pid = reader->video_pid; reader->output_audio_pid = reader->audio_pid; reader->output_pcr_pid = reader->pcr_pid; reader->output_pmt_pid = reader->pmt_pid; } } /* * Called to use the information extracted from a PMT packet. * * Note: don't free `pmt` after this call, as it is remembered * by the `reader`. * * Returns 0 if all goes well, 1 if something goes wrong. */ static int refine_TS_program_info(PES_reader_p reader, pmt_p pmt) { // If this is the *first* PMT, then just adopt its data wholesale if (reader->program_map == NULL) { reader->program_map = pmt; reader->pcr_pid = pmt->PCR_pid; #if DEBUG_PROGRAM_INFO if (reader->give_info) { fprint_msg("PMT packet at " OFFSET_T_FORMAT ": first PMT, used as-is\n", reader->posn); report_pmt(TRUE," ",reader->program_map); } #else if (reader->give_info) report_pmt(TRUE," ",reader->program_map); #endif // And use its information to determine our video/audio PIDs decide_pids(reader); reader->got_program_data = TRUE; return 0; } // Otherwise, check if this PMT contains the same information if (pmt->program_number != reader->program_number) { if (reader->give_info) fprint_msg("Ignoring PMT for program %d\n",pmt->program_number); free_pmt(&pmt); // since our caller will not free it return 0; } if (same_pmt(pmt,reader->program_map)) { free_pmt(&pmt); // since our caller will not free it return 0; } // Grumble or replace? Maybe both is safest if (reader->give_warning) { fprint_err("!!! PMT in TS packet at " OFFSET_T_FORMAT " replaces previous program information\n",reader->posn); print_err(" Program information was:\n"); report_pmt(FALSE," ",reader->program_map); print_err(" New program information is:\n"); report_pmt(FALSE," ",pmt); } else if (reader->give_info) { #if DEBUG_PROGRAM_INFO fprint_msg("PMT packet at " OFFSET_T_FORMAT ": updating program info\n", reader->posn); #endif report_pmt(TRUE," ",pmt); } free_pmt(&reader->program_map); reader->program_map = pmt; reader->pcr_pid = pmt->PCR_pid; // And use this new information to determine/check our video/audio PIDs decide_pids(reader); return 0; } /* * Call this on each PMT found for the program being read from TS, * to refine its idea of what is in that program. * * - `reader` is the PES reader context corresponding to the newly * opened file. * * Note that it is assumed that a particular program number will refer * to the same program stream throughout a TS file. * * Returns 0 if all goes well, 1 if something goes wrong. */ static int extract_and_refine_TS_program_info(PES_reader_p reader, uint32_t pmt_pid, byte pmt_data[], int pmt_data_len) { int err; pmt_p pmt = NULL; err = extract_pmt(FALSE,pmt_data,pmt_data_len,pmt_pid,&pmt); if (err) { print_err("### Error extracting stream list from PMT\n"); return 1; } // If it's the wrong program, we're not interested if (pmt->program_number != reader->program_number) { #if DEBUG_PROGRAM_INFO if (reader->give_info) fprint_msg("PMT packet at " OFFSET_T_FORMAT ": program number %d (not %d)\n", reader->posn,pmt->program_number,reader->program_number); #endif free_pmt(&pmt); return 0; } err = refine_TS_program_info(reader,pmt); if (err) { print_err("### Error refining TS program information from PMT\n"); free_pmt(&pmt); return 1; } // Mustn't free `pmt` because it is remembered by the reader return 0; } /* * Look up the (first) PAT in a Transport Stream * * - `reader` is the PES reader context corresponding to the newly * opened file. * - if `quiet` is not true, and the program number was given as 0, then * the function will report on finding the first PAT and what program * number it deduces therefrom. * * Note that it is assumed that a particular program number will refer * to the same program stream throughout a TS file. * * Returns 0 if all goes well, 1 if something goes wrong. */ static int find_first_PAT(PES_reader_p reader) { int err; int num_read; pidint_list_p prog_list = NULL; err = find_pat(reader->tsreader,0,FALSE,!reader->give_info, &num_read,&prog_list); if (err) { print_err("### Error finding first PAT\n"); return 1; } if (prog_list->length == 0) { fprint_err("### No programs defined in first PAT (at " OFFSET_T_FORMAT ")\n",reader->tsreader->posn - TS_PACKET_SIZE); free_pidint_list(&prog_list); return 1; } else if (prog_list->length > 1 && reader->give_info) print_msg("Multiple programs in PAT - using the first\n\n"); if (reader->program_number == 0) { reader->program_number = prog_list->number[0]; reader->pmt_pid = prog_list->pid[0]; } else { int ii; int got_program = FALSE; for (ii=0; iilength; ii++) { if (prog_list->number[ii] == reader->program_number) { got_program = TRUE; reader->pmt_pid = prog_list->pid[ii]; break; } } if (!got_program) { fprint_err("### Program %d not found in first PAT at " OFFSET_T_FORMAT "\n",reader->program_number, reader->tsreader->posn - TS_PACKET_SIZE); return 1; } } free_pidint_list(&prog_list); return 0; } /* * Look up the (first after the first PAT) PMT in a Transport Stream * * - `reader` is the PES reader context corresponding to the newly * opened file. * * Returns 0 if all goes well, 1 if something goes wrong, EOF if end-of-file * is found before the first (useful) PMT. */ static int find_first_PMT(PES_reader_p reader) { int err; int nread = 0; pmt_p pmt = NULL; for (;;) { err = find_next_pmt(reader->tsreader,reader->pmt_pid,-1,0, FALSE,!reader->give_info,&nread,&pmt); if (err) { fprint_err("### Error looking for program %d PMT with PID %04x" " after first PAT\n",reader->program_number,reader->pmt_pid); return 1; } if (pmt->program_number == reader->program_number) break; if (reader->give_info) fprint_msg("(Program is %d, not %d - ignoring it)\n", pmt->program_number,reader->program_number); free_pmt(&pmt); } err = refine_TS_program_info(reader,pmt); if (err) { print_err("### Error refining TS program information from PMT\n"); free_pmt(&pmt); return 1; } // Mustn't free `pmt` because it is remembered by the reader return 0; } /* * Find the first program information for a Transport Stream. * * Rewinds when it has finished. * * Find the first PAT, and then the first PMT following that, and thus * determine the program information. Then rewind. This won't hurt it the * TS is well formed, since one would expect PAT and PMT at the start of * the stream. However, if we're reading something taken from a broadcast * stream, then it is unlikely that the PAT/PMT would occur near the * beginning, but it *is* likely that the program information will be * the same throughout. Thus by rewinding, we can hope to interpret useful * packets that occur at the start of the stream. * * - `reader` is the PES reader context corresponding to the newly * opened file. * - if `quiet` is not true, and the program number was given as 0, then * the function will report on finding the first PAT and what program * number it deduces therefrom. * * Note that it is assumed that a particular program number will refer * to the same program stream throughout a TS file. * * Returns 0 if all goes well, 1 if something goes wrong. */ static int determine_TS_program_info(PES_reader_p reader) { int err; err = find_first_PAT(reader); if (err) { print_err("### Error finding TS program information\n"); return 1; } err = find_first_PMT(reader); if (err) { print_err("### Error finding TS program information\n"); return 1; } // It's only possible to rewind if we're not reading from standard // input. If it's not feasible, don't try. if (reader->tsreader->file != STDIN_FILENO) { err = seek_using_TS_reader(reader->tsreader, 0); if (err) { print_err("### Error rewinding TS stream after finding initial" " program information\n"); return 1; } // Having rewound, we mustn't forget to reset our notion of the TS packet // position reader->posn = 0; } return 0; } /* * Start a new PES packet, for a given PID/stream id * * - `reader` is our PES reader context * - `pid` is the PID of the TS packet we've just read * - `stream_type` is the stream type of this data * - `payload` is the TS packets payload * - `payload_len` is the length of said payload * - if the PES packet is finished (i.e., all of it has been read in) * then `finished` will be its data, otherwise `finished` will be NULL. * * Returns 0 if all went well, 1 if something went wrong */ static int start_new_PES_packet(PES_reader_p reader, uint32_t pid, byte payload[], int payload_len, PES_packet_data_p *finished) { int err; int index; PES_packet_data_p just_ended = NULL; PES_packet_data_p data; //fprint_msg("%c",(pid==reader->video_pid?'V':'A'));fflush(stdout); #if DEBUG_PES_ASSEMBLY fprint_msg(": start new %s PES packet, payload_len = %d\n", (pid==reader->video_pid?"video":"audio"),payload_len); print_data(TRUE,"Data",payload,payload_len,payload_len); #endif if (payload_len < 6) { // It is technically possible to start a PES packet with very few // bytes in its first TS packet, but I can't see why anyone would // do it (after all, the adaptation field itself cannot be more // than 30 bytes (I counted quickly, I might be off by one) long, // which leaves *lots* of space. So I shall assume that it is an // error if the first six bytes (at least) of PES data are not // in the first TS packet fprint_err("### Only first %d byte%s of PES packet in its first TS packet," " packet at " OFFSET_T_FORMAT "\n",payload_len, (payload_len==1?"":"s"),reader->posn); return 1; } if (payload[0] != 0 || payload[1] != 0 || payload[2] != 1) { fprint_err("### PES data starting in TS packet at " OFFSET_T_FORMAT " starts %02X %02X %02X, not 00 00 01\n", reader->posn,payload[0],payload[1],payload[2]); return 1; } // If PES packets with lengths of zero (i.e., meaning "unbounded") are // being transmitted, then we can only tell that we have reached their // end when we start the next PES packet for that same PID (or when we // reach EOF). index = pid_index_in_peslist(reader->packets,pid); if (index != -1 && reader->packets->data[index] != NULL && reader->packets->data[index]->length == 0) { #if DEBUG_PES_ASSEMBLY print_msg("@@@ just ended previous packet (by implication)\n"); #endif just_ended = reader->packets->data[index]; reader->packets->data[index] = NULL; } // Anyway, start a new PES packet for this TS packet's data #if DEBUG_PES_ASSEMBLY print_msg("@@@ start packet in PES list\n"); #endif err = start_packet_in_peslist(reader,pid,pid==reader->video_pid,&data); if (err) { fprint_err("### Error trying to start a new PES packet," " for TS packet " OFFSET_T_FORMAT "\n",reader->posn); return 1; } #if DEBUG_PES_ASSEMBLY fprint_msg("@@@ extend packet - data_len was %d\n",data->data_len); #endif err = extend_PES_packet_data(data,payload,payload_len); if (err) { print_err("### Error remembering data at start of PES packet\n"); return 1; } #if DEBUG_PES_ASSEMBLY fprint_msg("@@@ data_len is now %d\n",data->data_len); #endif data->length = ((payload[4] << 8) | payload[5]); if (data->length != 0) data->length += 6; // correct to the actual packet length #if DEBUG_PES_ASSEMBLY else print_msg("@@@ PES packet marked as length 0\n"); #endif data->posn = reader->posn; // Unlikely, but have we already finished our PES packet? if ((data->data_len > data->length) && data->length != 0) { #if ALLOW_OVERLONG_PACKETS int extra; fprint_err("### Found %d bytes of PES data, but expected %d" " (PES packet length + 6)\n",data->data_len,data->length); extra = data->data_len - data->length; if (extra > 0) { #if 0 int from = payload_len - extra; print_data(FALSE," End of data",payload+from,extra,extra); #endif fprint_err(" In %s PES packet, PID %x, starting at " OFFSET_T_FORMAT "\n",(pid==reader->video_pid?"video":"audio"), pid,reader->posn); print_err("!!! Accepting packet anyway\n"); *finished = data; err = clear_packet_in_peslist(reader->packets,pid); if (err) return 1; } else return 1; #else fprint_err("### Found %d bytes of PES data, but expected %d" " (PES packet length + 6)\n",data->data_len,data->length); return 1; #endif } else if (data->length == data->data_len) { *finished = data; err = clear_packet_in_peslist(reader->packets,pid); if (err) return 1; } else *finished = NULL; // And if we had a packet ended by this packet starting, defer this // result until later... if (just_ended) { reader->deferred = *finished; // which might *not* be NULL *finished = just_ended; } return 0; } /* * Continue a PES packet, for a given PID/stream id * * - `reader` is our PES reader context * - `pid` is the PID of the TS packet we've just read * - `stream_type` is the stream type of this data * - `payload` is the TS packets payload * - `payload_len` is the length of said payload * - if the PES packet is finished (i.e., all of it has been read in) * then `finished` will be its data, otherwise `finished` will be NULL. * * Returns 0 if all went well, 1 if something went wrong */ static int continue_PES_packet(PES_reader_p reader, uint32_t pid, byte payload[], int payload_len, PES_packet_data_p *finished) { int err; PES_packet_data_p data; #if DEBUG_PES_ASSEMBLY fprint_msg(": continue %s PES packet\n", (pid==reader->video_pid?"video":"audio")); #endif err = find_packet_in_peslist(reader->packets,pid,&data); if (err || data == NULL) { if (reader->give_warning) fprint_err("!!! TS packet with PID %04x at " OFFSET_T_FORMAT " continues an unstarted PES packet - ignoring it\n", pid,reader->posn); *finished = NULL; return 0; } //fprint_msg("%c",(pid==reader->video_pid?'v':'a'));fflush(stdout); err = extend_PES_packet_data(data,payload,payload_len); if (err) { print_err("### Error remembering data to continue PES packet\n"); return 1; } if ((data->data_len > data->length) && data->length != 0) { #if ALLOW_OVERLONG_PACKETS int extra; fprint_err("### Found %d bytes of PES data, but expected %d" " (PES packet length + 6)\n",data->data_len,data->length); extra = data->data_len - data->length; if (extra > 0) { #if 0 int from = payload_len - extra; print_data(FALSE," End of data",payload+from,extra,extra); #endif fprint_err(" In %s PES packet, PID %x, starting at " OFFSET_T_FORMAT "\n",(pid==reader->video_pid?"video":"audio"), pid,reader->posn); print_err("!!! Accepting packet anyway\n"); *finished = data; err = clear_packet_in_peslist(reader->packets,pid); if (err) return 1; } else return 1; #else fprint_err("### Found %d bytes of PES data, but expected %d" " (PES packet length + 6)\n",data->data_len,data->length); return 1; #endif } else if (data->data_len == data->length) { *finished = data; err = clear_packet_in_peslist(reader->packets,pid); if (err) return 1; } else *finished = NULL; return 0; } /* * Check for a PES packet legitimately ended by EOF * * - `reader` is a PES reader context * - `packet_data` is either NULL (meaning no more packets to return), * or a PES packet whose length was specified as 0, meaning that it * would be ended by the next PES packet with the same PID, or by EOF. * * Note that a PES packet length of 0 is *only* allowed for video ES * within TS, and we are only attempting to cope with a single video * stream (per program), so we need only expect to have to check for * one "outstanding" stream when we hit EOF. */ static void check_for_EOF_packet(PES_reader_p reader, PES_packet_data_p *packet_data) { int ii; // Not trying to be very efficient - shouldn't matter for (ii = 0; ii < reader->packets->length; ii++) { if (reader->packets->data[ii] != NULL && reader->packets->data[ii]->length == 0) { *packet_data = reader->packets->data[ii]; reader->packets->data[ii] = NULL; return; } } *packet_data = NULL; return; } /* * Return the next PES packet from the input TS file * * - `reader` is a PES reader context * - `packet_data` is the packet data (NULL if EOF is read) * * Returns 0 if all goes well, EOF if end of file is read, and 1 if * something goes wrong. */ static int read_next_PES_packet_from_TS(PES_reader_p reader, PES_packet_data_p *packet_data) { // If we have a packet "in hand" because we read it earlier, then // just return it if (reader->deferred) { if (reader->video_only && !reader->deferred->is_video) { free_PES_packet_data(&reader->deferred); } else { *packet_data = reader->deferred; reader->deferred = NULL; #if DEBUG_PES_ASSEMBLY print_msg("@@@ returning deferred PES packet\n"); #endif return 0; } } // If we had read EOF earlier (but not said so because of a PES // packet being finished by EOF), then admit to it now if (reader->had_eof) { *packet_data = NULL; return EOF; } for (;;) { int err; byte *ts_packet; int payload_unit_start_indicator; byte *adapt; int adapt_len; byte *payload; int payload_len; uint32_t pid; // Remember the position of the packet we're going to read reader->posn = reader->tsreader->posn; // And read it // Remember that `ts_packet` will not persist, as it is a pointer // into the TS buffering innards err = read_next_TS_packet(reader->tsreader,&ts_packet); if (err == EOF) { // If we've been given EOF, then either we're just *read* EOF // instead of a packet (the obvious case), or we read some data // that was terminated by EOF last time, and the EOF was "deferred" // by the underlying buffering methods. // So, just in case, we'll check for an unbounded (length marked as // zero) video stream PES packet check_for_EOF_packet(reader,packet_data); if (*packet_data == NULL) return EOF; else return 0; } else if (err) { fprint_err("### Error reading TS packet at " OFFSET_T_FORMAT "\n", reader->posn); return 1; } err = split_TS_packet(ts_packet,&pid,&payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err) { fprint_err("### Error interpreting TS packet at " OFFSET_T_FORMAT "\n", reader->posn); return 1; } #if DEBUG_PES_ASSEMBLY fprint_msg("@@@ TS packet at " OFFSET_T_FORMAT " with pid %3x", reader->posn,pid); #endif // If we're writing out TS packets directly to a client, then this // is probably a sensible place to do it. if (reader->write_TS_packets && reader->tswriter != NULL && !reader->suppress_writing) { err = tswrite_write(reader->tswriter,ts_packet,pid,FALSE,0); if (err) { fprint_err("### Error writing TS packet (PID %04x) at " OFFSET_T_FORMAT "\n",pid,reader->posn); return 1; } } if (pid == 0) // PAT { // XXX We should probably check that the PAT for our program // has not changed... #if DEBUG_PES_ASSEMBLY print_msg(": PAT\n"); #endif } else if (pid == reader->pmt_pid) { #if DEBUG_PES_ASSEMBLY print_msg(": PMT\n"); #endif if (payload_unit_start_indicator && reader->pmt_data) { // This is the start of a new PMT packet, but we'd already // started one, so throw its data away fprint_err("!!! Discarding previous (uncompleted) PMT data at " OFFSET_T_FORMAT "\n",reader->posn); free(reader->pmt_data); reader->pmt_data = NULL; reader->pmt_data_len = reader->pmt_data_used = 0; } else if (!payload_unit_start_indicator && !reader->pmt_data) { // This is the continuation of a PMT packet, but we hadn't // started one yet fprint_err("!!! Discarding PMT continuation, no PMT started, at " OFFSET_T_FORMAT "\n",reader->posn); continue; } err = build_psi_data(FALSE,payload,payload_len,pid, &reader->pmt_data, &reader->pmt_data_len, &reader->pmt_data_used); if (err) { fprint_err("### Error %s PMT at " OFFSET_T_FORMAT "\n", (payload_unit_start_indicator?"starting new":"continuing"), reader->posn); if (reader->pmt_data) free(reader->pmt_data); return 1; } // Do we need more data to complete this PMT? if (reader->pmt_data_len > reader->pmt_data_used) continue; err = extract_and_refine_TS_program_info(reader,pid, reader->pmt_data, reader->pmt_data_len); if (err) { fprint_err("### Error updating program info from PMT" " (TS packet at " OFFSET_T_FORMAT ")\n",reader->posn); if (reader->pmt_data) free(reader->pmt_data); return 1; } free(reader->pmt_data); reader->pmt_data = NULL; reader->pmt_data_len = reader->pmt_data_used = 0; if (reader->write_PES_packets && !reader->suppress_writing) { // XXX We *probably* should check if it's changed before doing this, // but at least by outputting it again we ensure it's current err = write_program_data(reader,reader->tswriter); if (err) return 1; // At least make sure it doesn't get written again *too* soon reader->program_index = reader->program_freq; } } else if (payload_len > 0 && (pid == reader->video_pid || (pid == reader->audio_pid && !reader->video_only))) { // It's a packet we're interested in PES_packet_data_p finished; if (payload_unit_start_indicator) err = start_new_PES_packet(reader,pid,payload,payload_len, &finished); else err = continue_PES_packet(reader,pid,payload,payload_len, &finished); if (err) { fprint_err("### Error %s PES packet (PID %04x)" " with TS packet at " OFFSET_T_FORMAT "\n", (payload_unit_start_indicator?"starting":"continuing"), pid,reader->posn); print_data(FALSE," Data",payload,payload_len,20); return 1; } if (finished) { #if DEBUG_PES_ASSEMBLY fprint_msg("@@@ PES packet with pid %x finished\n",pid); report_PES_data_array(" ",finished->data,finished->data_len,TRUE); #endif if (pid == reader->audio_pid && reader->video_only) { // Actually, they aren't interested in audio at the moment // (we check this now because the user can alter this over // time, and when we *started* collecting the packet, they // might have said they *were* interested in audio) free_PES_packet_data(&finished); } else { #if DEBUG_PES_ASSEMBLY print_msg("@@@ return it\n"); #endif *packet_data = finished; break; } } } #if DEBUG_PES_ASSEMBLY else print_msg("\n"); #endif } return 0; } // ============================================================ // General functionality // ============================================================ /* * Look at the start of a file to determine if it appears to be transport * stream. Rewinds the file when it is finished. * * The file is assumed to be Transport Stream if it starts with 0x47 as * the first byte, and 0x47 recurs at 188 byte intervals (in other words, * it appears to start with several TS packets). * * - `input` is the file to check * - `is_TS` is TRUE if it looks like TS, as described above. * * Returns 0 if all goes well, 1 if there was an error. */ extern int determine_if_TS_file(int input, int *is_TS) { int err; int ii; byte buf[TS_PACKET_SIZE]; *is_TS = TRUE; for (ii = 0; ii < 100; ii++) { err = read_bytes(input,TS_PACKET_SIZE,buf); if (err == EOF) break; else if (err) { print_err("### Error trying to check if file is TS\n"); return 1; } if (buf[0] != 0x47) { *is_TS = FALSE; break; } } err = seek_file(input,0); if (err) { print_err("### Error rewinding file after determining if it is TS\n"); return 1; } return 0; } /* * Given a PES stream (PS or TS), attempt to determine if it holds H.262 or * H.264 data. Sets the `video_type` flag on the reader appropriately. * * (In fact, this is only needed for PS data, as TS data "says" what it * contains in the PAT/PMT.) * * - `input` is the file to check * * Returns 0 if all goes well, 1 if there was an error (including the * stream not appearing to be either). */ static int determine_PES_video_type(PES_reader_p reader) { int err; ES_p es; int old_video_only = reader->video_only; err = build_elementary_stream_PES(reader,&es); if (err) { print_err("### Error starting elementary stream before" " working out if PS is H.262 or H.264\n"); return 1; } reader->video_only = TRUE; err = decide_ES_video_type(es,FALSE,FALSE,&reader->video_type); if (err) { print_err("### Error deciding on PS video type\n"); free_elementary_stream(&es); return 1; } free_elementary_stream(&es); reader->is_h264 = (reader->video_type == VIDEO_H264); reader->video_only = old_video_only; err = rewind_program_stream(reader->psreader); if (err) { print_err("### Error rewinding PS stream after determining its type\n"); return 1; } return 0; } /* * Build a PES reader datastructure * * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the resulting PES reader * * Returns 0 if all goes well, 1 if something goes wrong. */ static int build_PES_reader_datastructure(int give_info, int give_warnings, PES_reader_p *reader) { int err; PES_reader_p new = malloc(SIZEOF_PES_READER); if (new == NULL) { print_err("### Unable to allocate PES reader datastructure\n"); return 1; } new->tsreader = NULL; // for the moment, at least new->psreader = NULL; // for the moment, at least new->is_TS = FALSE; // for want of better new->give_info = give_info; new->give_warning = give_warnings; new->posn = 0; new->is_h264 = FALSE; new->video_type = VIDEO_UNKNOWN; new->packet = NULL; new->program_number = 0; new->program_map = NULL; new->video_only = FALSE; new->audio_stream_id = 0; new->pmt_data = NULL; new->pmt_data_len = 0; new->pmt_data_used = 0; new->video_pid = new->audio_pid = 0; new->pcr_pid = new->pmt_pid = 0; new->got_program_data = FALSE; new->output_program_number = 0; new->output_video_pid = new->output_audio_pid = 0; new->output_pcr_pid = new->output_pmt_pid = 0; new->override_program_data = FALSE; new->output_dolby_stream_type = new->dolby_stream_type = DVB_DOLBY_AUDIO_STREAM_TYPE; new->override_dolby_stream_type = FALSE; new->tswriter = NULL; new->write_PES_packets = FALSE; new->write_TS_packets = FALSE; new->suppress_writing = TRUE; new->dont_write_current_packet = FALSE; new->pes_padding = 0; new->debug_read_packets = FALSE; err = build_peslist(&new->packets); if (err) { print_err("### Error building PES reader datastructure\n"); free(new); return 1; } new->deferred = NULL; new->had_eof = FALSE; *reader = new; return 0; } /* * Build a PES reader datastructure for PS data * * - `ps` is the Program Stream to read the PES data from * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the resulting PES reader * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_PS_PES_reader(PS_reader_p ps, int give_info, int give_warnings, PES_reader_p *reader) { int err; err = build_PES_reader_datastructure(give_info,give_warnings,reader); if (err) return 1; (*reader)->is_TS = FALSE; (*reader)->psreader = ps; // Try to determine what sort of video this is (particularly, is it H.264) err = determine_PES_video_type(*reader); if (err) { print_err("### Error determining PS stream type\n"); (void) free_PES_reader(reader); return 1; } return 0; } /* * Build a PES reader datastructure for TS data * * - `tsreader` is the Transport Stream to read the PES data from * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `program_number` is only used for TS data, and identifies which program * to read. If this is 0 then the first program encountered in the first PAT * will be read. * - `reader` is the resulting PES reader * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_TS_PES_reader(TS_reader_p tsreader, int give_info, int give_warnings, uint16_t program_number, PES_reader_p *reader) { int err; err = build_PES_reader_datastructure(give_info,give_warnings,reader); if (err) return 1; (*reader)->is_TS = TRUE; (*reader)->tsreader = tsreader; (*reader)->program_number = program_number; (*reader)->output_program_number = program_number; // Work out the program information by reading the first PAT and // the first (following) PMT err = determine_TS_program_info(*reader); if (err) { print_err("### Error determining/checking program number\n"); (void) free_PES_reader(reader); return 1; } return 0; } /* * Build a PES reader datastructure * * - `input` is the file to read the PES data from * - `is_TS` should be TRUE if the data is TS, FALSE if it is PS * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `program_number` is only used for TS data, and identifies which program * to read. If this is 0 then the first program encountered in the first PAT * will be read. * - `reader` is the resulting PES reader * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_PES_reader(int input, int is_TS, int give_info, int give_warnings, uint16_t program_number, PES_reader_p *reader) { int err; if (is_TS) { TS_reader_p tsreader; err = build_TS_reader(input,&tsreader); if (err) { print_err("### Error building TS specific reader\n"); return 1; } err = build_TS_PES_reader(tsreader,give_info,give_warnings,program_number, reader); if (err) { print_err("### Error building TS specific reader\n"); free_TS_reader(&tsreader); return 1; } } else { PS_reader_p ps; err = build_PS_reader(input,!give_info,&ps); if (err) { print_err("### Error building PS specific reader\n"); return 1; } err = build_PS_PES_reader(ps,give_info,give_warnings,reader); if (err) { print_err("### Error building PS specific reader\n"); free_PS_reader(&ps); return 1; } } return 0; } /* * Open a Transport Stream file for PES packet reading * * - `filename` is the name of the file to open. * - `program_number` identifies which program to read. If this is 0 * then the first program encountered in the first PAT will be read. * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). If information messages are requested, and the * program number is given as 0, the actual program number chosen will * be reported as well. * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the PES reader context corresponding to the newly * opened file. * * Note that it is assumed that a particular program number will refer * to the same program stream throughout a TS file. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int open_PES_reader_for_TS(char *filename, uint16_t program_number, int give_info, int give_warnings, PES_reader_p *reader) { int err; int input; input = open_binary_file(filename,FALSE); if (input == -1) { fprint_err("### Unable to open input TS file %s\n",filename); return 1; } err = build_PES_reader(input,TRUE,give_info,give_warnings,program_number, reader); if (err) return 1; return 0; } /* * Open a Program Stream file for PES packet reading * * - `filename` is the name of the file to open. * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the PES reader context corresponding to the newly * opened file. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int open_PES_reader_for_PS(char *filename, int give_info, int give_warnings, PES_reader_p *reader) { int input = open_binary_file(filename,FALSE); if (input == -1) { fprint_err("### Unable to open input PS file %s\n",filename); return 1; } return build_PES_reader(input,FALSE,give_info,give_warnings,0,reader); } /* * Open a Program Stream or Transport Stream file for PES packet reading * * - `filename` is the name of the file to open. * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the PES reader context corresponding to the newly * opened file. * * If the file is Transport Stream, then this is equivalent to a call * of:: * * err = open_PES_reader_for_TS(filename,0,give_info,give_warnings,&reader); * * i.e., the first program found is the program that will be read. * * The default behaviour in retrieving video and audio is: * * - Assume a single video stream, and retrieve any video packets * - For TS data, assume a single audio stream in the requested * program, and return that, regardless of its type. If there * is more than one audio stream in the program, it is not * defined which will be chosen. * - For PS data, return the first non-Dolby audio stream encountered * (and thus no audio if no non-Dolby stream is found). * * To request video only, call `set_PES_reader_video_only()`. * * To request a specific audio stream from PS data, call * `set_PES_reader_audio_stream()` (this may be called for TS data * as well, but will have no effect). * * To request Dolby audio, call `set_PES_reader_audio_private1()` * (again this will have no effect on TS data). * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int open_PES_reader(char *filename, int give_info, int give_warnings, PES_reader_p *reader) { int err; int input; int is_TS; input = open_binary_file(filename,FALSE); if (input == -1) { fprint_err("### Unable to open input file %s\n",filename); return 1; } err = determine_if_TS_file(input,&is_TS); if (err) { (void) close_file(input); return 1; } if (is_TS) return build_PES_reader(input,TRUE,give_info,give_warnings,0,reader); else return build_PES_reader(input,FALSE,give_info,give_warnings,0,reader); } /* * Tell the PES reader whether we only want video data * * - `video_only` should be TRUE if audio is to be ignored, FALSE * if audio should be retained. * * By default, the PES reader returns video data and a single audio * stream (taken from the first audio stream encountered). */ extern void set_PES_reader_video_only(PES_reader_p reader, int video_only) { reader->video_only = video_only; return; } /* * Tell the PES reader which audio stream we want. * * By default, the PES reader returns video data and a single audio * stream (taken from the first audio stream encountered). * * - `reader` is the PES reader context * - `stream_number` is the number of the audio stream to read, from * 0 to 31 (0x1F). * * This call only has effect if the input data is PS. * * Returns 0 if all went well, or 1 if there was an error (specifically, * that `stream_number` was not in the range 0-31). */ extern int set_PES_reader_audio_stream(PES_reader_p reader, int stream_number) { if (stream_number < 0 || stream_number > 0x1F) { fprint_err("### Audio stream number %d is not in range 0-31\n", stream_number); return 1; } reader->audio_stream_id = 0xc0 | stream_number; return 0; } /* * Tell the PES reader to get its audio stream from private stream 1 * (this is the stream that is conventionally used for Dolby (AC-3) * in DVD data). * * By default, the PES reader returns video data and a single audio * stream (taken from the first audio stream encountered). * * - `reader` is the PES reader context * * This call only has effect if the input data is PS. */ extern void set_PES_reader_audio_private1(PES_reader_p reader) { reader->audio_stream_id = PRIVATE1_AUDIO_STREAM_ID; return; } /* * Tell the PES reader to use the given program information when outputting. * * For PS data (which does not contain TS program information), this is simply * a means of setting up sensible values. * * For TS data, this sets the values to use for output when writing to TS, * so that they may be different from those being read in. * * The above means that it may only be called after it is known whether * the data being read is PS or TS data. * * Note that calling it more than once is allowed - it will happily * overwrite any previous values. * * - `reader` is the PES reader context * - `program_number` is the program number to assume. If this is 0, * then 1 will be used. * - `pmt_pid` is the PID to use for the PMT. * - `video_pid` is the PID to use for video data * - `audio_pid` is the PID to use for audio data (if any) * - `pcr_pid` is the PID to use for PCR data - this will often * be the same as the `video_pid` */ extern void set_PES_reader_program_data(PES_reader_p reader, uint16_t program_number, uint32_t pmt_pid, uint32_t video_pid, uint32_t audio_pid, uint32_t pcr_pid) { if (program_number == 0) program_number = 1; if (reader->is_TS) { reader->override_program_data = TRUE; reader->output_program_number = program_number; reader->output_pmt_pid = pmt_pid; reader->output_pcr_pid = pcr_pid; reader->output_video_pid = video_pid; reader->output_audio_pid = audio_pid; } else { reader->output_program_number = reader->program_number = program_number; reader->output_pmt_pid = reader->pmt_pid = pmt_pid; reader->output_pcr_pid = reader->pcr_pid = pcr_pid; reader->output_video_pid = reader->video_pid = video_pid; reader->output_audio_pid = reader->audio_pid = audio_pid; reader->got_program_data = TRUE; // Ideally we might also set the reader->program_map datastructure up } } /* * Tell the PES reader that the PS data it is reading is MPEG-4/AVC, * as opposed to MPEG-1/MPEG-2. */ extern void set_PES_reader_h264(PES_reader_p reader) { reader->is_h264 = TRUE; reader->video_type = VIDEO_H264; } /* * Tell the PES reader that the PS data it is reading is of * type `video_type` (which is assumed to be a legitimate value * such as VIDEO_H264, etc.) */ extern void set_PES_reader_video_type(PES_reader_p reader, int video_type) { reader->video_type = video_type; reader->is_h264 = (video_type == VIDEO_H264); } /* * Tell the PES reader whether to output any Dolby (AC-3) audio data * it may read using the DVB stream type (0x06) or the ATSC stream * type (0x81). * * If it is reading TS data, then the default is to use whatever stream type * the Dolby audio was read with. * * If it is reading PS data, then the default is to assume DVB data. * * This call only has effect if Dolby audio data is actually selected. */ extern void set_PES_reader_dolby_stream_type(PES_reader_p reader, int is_dvb) { reader->override_dolby_stream_type = TRUE; reader->output_dolby_stream_type = (is_dvb?DVB_DOLBY_AUDIO_STREAM_TYPE: ATSC_DOLBY_AUDIO_STREAM_TYPE); } /* * Reposition the PES reader to an earlier packet * * It is the caller's responsibility to choose a sensible `posn` to seek to. * * Note that using this to reposition in a PES reader does not affect any * "higher" reading context using this PES reader - specifically, if data * is being read via an ES reader, then calling this function directly * will result in the ES reader losing its positional information. * * In this case, `seek_ES` should be called. * * - `reader` is the PES reader context * - `posn` is a packet position obtained from an earlier PES packet * datastructure (this should *not* be a random offset in the input * file, as that will not in general work). * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int set_PES_reader_position(PES_reader_p reader, offset_t posn) { int err; // The positioning is easy if (reader->is_TS) err = seek_using_TS_reader(reader->tsreader,posn); else err = seek_using_PS_reader(reader->psreader,posn); if (err) return 1; // (although it's important not to forget to set the TS packet position for // the next packet...) reader->posn = posn; // But we must also make sure that we've lost any memory of previous // PES packet data that we were building up if (reader->is_TS) { int ii; for (ii=0; iipackets->length; ii++) free_PES_packet_data(&reader->packets->data[ii]); if (reader->deferred) free_PES_packet_data(&reader->deferred); reader->had_eof = FALSE; } return 0; } /* * Free a PES reader, and the relevant datastructures. Does not close * the underlying file. * * - `reader` is the PES reader context. This will be freed, and * returned as NULL. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int free_PES_reader(PES_reader_p *reader) { int err = 0; if ((*reader) == NULL) return 0; if ((*reader)->packet != NULL) free_PES_packet_data(&(*reader)->packet); // Forget any file (*reader)->tsreader = NULL; (*reader)->psreader = NULL; if ((*reader)->program_map != NULL) { free_pmt(&(*reader)->program_map); } if ((*reader)->pmt_data != NULL) { free((*reader)->pmt_data); (*reader)->pmt_data = NULL; (*reader)->pmt_data_len = 0; (*reader)->pmt_data_used = 0; } if ((*reader)->packets != NULL) { free_peslist(&(*reader)->packets); } if ((*reader)->is_TS) free_TS_reader(&(*reader)->tsreader); else free_PS_reader(&(*reader)->psreader); free(*reader); *reader = NULL; return err; } /* * Close a PES reader, and free the relevant datastructures. * * - `reader` is the PES reader context. This will be freed, and * returned as NULL. * * Returns 0 if all goes well, 1 if something goes wrong with closing the * file (although in that case, the `reader` will still have been freed). */ extern int close_PES_reader(PES_reader_p *reader) { int err = 0; int err2; if ((*reader) == NULL) return 0; if ((*reader)->is_TS) { if ((*reader)->tsreader != NULL) { err = close_TS_reader(&(*reader)->tsreader); if (err) print_err("### Error closing TS reader\n"); } } else { if ((*reader)->psreader != NULL) { err = close_PS_file(&(*reader)->psreader); if (err) print_err("### Error closing PS reader\n"); } } err2 = free_PES_reader(reader); if (err) return err; else return err2; } /* * Read in the next PES packet from the input file * * - `reader` is a PES reader context * * Returns 0 if all goes well, EOF if end of file is read, and 1 if * something goes wrong. */ extern int read_next_PES_packet(PES_reader_p reader) { int err; if (reader->packet != NULL) { if (reader->write_PES_packets && reader->tswriter != NULL && !reader->suppress_writing && !reader->dont_write_current_packet) { // Aha - we need to output the previous PES packet uint32_t pid; byte stream_id; if (reader->program_index == 0) { // Output the current program information err = write_program_data(reader,reader->tswriter); if (err) return 1; reader->program_index = reader->program_freq; } else reader->program_index --; #if DEBUG_READ_PACKETS if (reader->debug_read_packets) { fprint_msg("<packet->posn, reader->packet->data_len); if (reader->packet->is_video) { fprint_msg(" VIDEO eslen %d",reader->packet->es_data_len); if (reader->packet->data_alignment_indicator) print_msg(" aligned"); } print_msg(">>\n"); } #endif if (reader->packet->is_video) { pid = reader->output_video_pid; stream_id = DEFAULT_VIDEO_STREAM_ID; } else { pid = reader->output_audio_pid; stream_id = DEFAULT_AUDIO_STREAM_ID; } err = write_PES_as_TS_PES_packet(reader->tswriter, reader->packet->data, reader->packet->data_len, pid,stream_id,FALSE,0,0); if (err) { print_err("### Error writing out PES packet as TS\n"); return 1; } if (reader->pes_padding) { // Add some "dummy" PES packets to bulk out our output int ii; PES_packet_data_p dummy; err = build_dummy_PES_packet_data(&dummy,reader->packet->data_len); if (err) return 1; for (ii = 0; ii < reader->pes_padding; ii++) { err = write_PES_as_TS_PES_packet(reader->tswriter, dummy->data,dummy->data_len, pid,STREAM_ID_PADDING_STREAM,FALSE,0,0); if (err) { print_err("### Error writing out dummy PES packet as TS\n"); return 1; } } } } // And it's our job to free each PES packet as it is no longer needed free_PES_packet_data(&reader->packet); } // We always undo the "don't write the current packet flag" after we (might) // have written it out reader->dont_write_current_packet = FALSE; if (reader->is_TS) err = read_next_PES_packet_from_TS(reader,&reader->packet); else err = read_next_PES_packet_from_PS(reader,&reader->packet); #if DEBUG_READ_PACKETS if (reader->debug_read_packets) { if (err==EOF) print_msg("<>\n"); else if (!err) fprint_msg("<>\n", reader->packet->posn); } #endif // Higher layers want to know if a particular PES packet had a PTS or not if (!err) reader->packet->has_PTS = PES_packet_has_PTS(reader->packet); return err; } // ============================================================ // Reading bytes from PES packets // ============================================================ /* * Given an MPEG-1 PES packet, determine the offset of the ES data. * * - `data` is the PES packet data, starting "00 00 01 * " * - `data_len` is the actual length of the data therein * * Returns the required offset (i.e., packet[offset] is the first byte * of the ES data within the PES packet). */ extern int calc_mpeg1_pes_offset(byte *data, int data_len) { int posn = 6; while (posn < data_len && data[posn] == 0xFF) // ignore padding bytes posn++; // (should be <= 16, but...) if (posn < data_len) { if ((data[posn] & 0xC0) == 0x40) // ignore buffer scale/size posn += 2; if ((data[posn] & 0xF0) == 0x20) // ignore PTS posn += 5; else if ((data[posn] & 0xF0) == 0x30) // ignore PTS and DTS posn += 10; else if (data[posn] == 0x0F) // check for paranoia posn ++; else { fprint_err("### MPEG-1 PES packet has 0x%1xX" " instead of 0x40, 0x2X, 0x3X or 0x0F\n",(data[posn]&0xF0)>>4); posn ++; // what else can we do? } } return posn; } /* * Set up ES data access for this PES packet - i.e., set up the `es_data` * array as an offset into the PES packet's `data` array. * * For packets that do not contain any ES data (including PSM, etc.), * a zero length ES data array will be set. * * Note that only packets that appear to be video (i.e., have their * `is_video` flag set) will be considered as potential ES data packets. * * - `packet` is the PES packet datastructure */ static inline void setup_PES_as_ES(PES_packet_data_p packet) { byte stream_id; int offset; if (!packet->is_video) { packet->es_data = packet->data + 6; // Perhaps safer than using NULL packet->es_data_len = 0; return; } stream_id = packet->data[3]; switch (stream_id) { case STREAM_ID_PROGRAM_STREAM_MAP: case STREAM_ID_PRIVATE_STREAM_2: case STREAM_ID_ECM_STREAM: case STREAM_ID_EMM_STREAM: case STREAM_ID_PROGRAM_STREAM_DIRECTORY: case STREAM_ID_DSMCC_STREAM: case STREAM_ID_H222_E_STREAM: // There is data, but it's not ES data packet->es_data = packet->data + 6; // Perhaps safer than using NULL packet->es_data_len = 0; return; case STREAM_ID_PADDING_STREAM: // There's no data, it's just padding bytes packet->es_data = packet->data + 6; // Perhaps safer than using NULL packet->es_data_len = 0; return; default: break; // Otherwise, we assume ES data } // We shan't "pull apart" PTS and DTS unless the user asks specifically // So we just need to work out where out data is... // The first two bits of the PES header flags should be '10' for H.222.0 if (IS_H222_PES(packet->data)) { // Yes, it's H.222.0 // We have to discount: // * 3 bytes of packet_start_code_prefix (00 00 01) // * 1 byte of stream_id // * 2 bytes of PES_packet_length // * 2 bytes of PES_header_flags // * 1 byte of PES_header_data_length -- i.e., 9 bytes thus far // * PES_header_data_length bytes of PES header data // before we get to our ES data int PES_header_data_length = packet->data[8]; offset = 9 + PES_header_data_length; // The data alignment indicator seems like a sensible thing to remember packet->data_alignment_indicator = (packet->data[6] & 0x04) >> 2; } else { // We assume it's MPEG-1 offset = calc_mpeg1_pes_offset(packet->data,packet->data_len); } packet->es_data = packet->data + offset; packet->es_data_len = packet->data_len - offset; #if 0 // XXX print_data(TRUE," ",packet->es_data,packet->es_data_len,20); #endif #ifdef DEBUG if (reader->give_info) print_data(TRUE,".. ES data",packet->es_data,packet->es_data_len,20); #endif return; } /* * Read in the next PES packet that contains ES data we are interested in. * Ignores non-video packets. * * - `reader` is a PES reader context * * Returns 0 if all goes well, EOF if end of file is read, and 1 if * something goes wrong. */ extern int read_next_PES_ES_packet(PES_reader_p reader) { for (;;) { int err = read_next_PES_packet(reader); if (err) return err; // either 1 or EOF #ifdef DEBUG if (reader->give_info) { fprint_msg(".. PES packet at " OFFSET_T_FORMAT " is %x (", reader->packet->posn,reader->packet->data[3]); print_stream_id(TRUE,reader->packet->data[3]); fprint_msg(")%s\n",(reader->packet->is_video?" VIDEO":"")); } #endif if (reader->packet->is_video) { if (reader->debug_read_packets) report_PES_data_array("",reader->packet->data,reader->packet->data_len, TRUE); // Locate its ES data, and check we have some... setup_PES_as_ES(reader->packet); if (reader->packet->es_data_len > 0) break; } } return 0; } // ============================================================ // PES dissection // ============================================================ /* * Decode a PTS or DTS value. * * - `data` is the 5 bytes containing the encoded PTS or DTS value * - `required_guard` should be 2 for a PTS alone, 3 for a PTS before * a DTS, or 1 for a DTS after a PTS * - `value` is the PTS or DTS value as decoded * * Returns 0 if the PTS/DTS value is decoded successfully, 1 if an error occurs */ extern int decode_pts_dts(byte data[], int required_guard, uint64_t *value) { uint64_t pts1,pts2,pts3; int marker; char *what; int guard = (data[0] & 0xF0) >> 4; // Rather than try to use casts to make the arithmetic come out right on both // Linux-with-gcc (old-style C rules) and Windows-with-VisualC++ (C99 rules), // it's simpler just to use intermediates that won't get cast to "int". unsigned int data0 = data[0]; unsigned int data1 = data[1]; unsigned int data2 = data[2]; unsigned int data3 = data[3]; unsigned int data4 = data[4]; switch (required_guard) { case 2: what = "PTS"; break; // standalone case 3: what = "PTS"; break; // before a DTS case 1: what = "DTS"; break; // always after a PTS default: what = "PTS/DTS"; break; // surely some mistake? } if (guard != required_guard) { fprint_err("!!! Guard bits at start of %s data are %x, not %x\n", what,guard,required_guard); } pts1 = (data0 & 0x0E) >> 1; marker = data0 & 0x01; if (marker != 1) { fprint_err("### First %s marker is not 1",what); return 1; } pts2 = (data1 << 7) | ((data2 & 0xFE) >> 1); marker = data2 & 0x01; if (marker != 1) { fprint_err("### Second %s marker is not 1",what); return 1; } pts3 = (data3 << 7) | ((data4 & 0xFE) >> 1); marker = data4 & 0x01; if (marker != 1) { fprint_err("### Third %s marker is not 1",what); return 1; } *value = (pts1 << 30) | (pts2 << 15) | pts3; return 0; } /* * Encode a PTS or DTS. * * - `data` is the array of 5 bytes into which to encode the PTS/DTS * - `guard_bits` are the required guard bits: 2 for a PTS alone, 3 for * a PTS before a DTS, or 1 for a DTS after a PTS * - `value` is the PTS or DTS value to be encoded */ extern void encode_pts_dts(byte data[], int guard_bits, uint64_t value) { int pts1,pts2,pts3; #define MAX_PTS_VALUE 0x1FFFFFFFFLL if (value > MAX_PTS_VALUE) { char *what; uint64_t temp = value; while (temp > MAX_PTS_VALUE) temp -= MAX_PTS_VALUE; switch (guard_bits) { case 2: what = "PTS alone"; break; case 3: what = "PTS before DTS"; break; case 1: what = "DTS after PTS"; break; default: what = "PTS/DTS/???"; break; } fprint_err("!!! value " LLU_FORMAT " for %s is more than " LLU_FORMAT " - reduced to " LLU_FORMAT "\n",value,what,MAX_PTS_VALUE,temp); value = temp; } pts1 = (int)((value >> 30) & 0x07); pts2 = (int)((value >> 15) & 0x7FFF); pts3 = (int)( value & 0x7FFF); data[0] = (guard_bits << 4) | (pts1 << 1) | 0x01; data[1] = (pts2 & 0x7F80) >> 7; data[2] = ((pts2 & 0x007F) << 1) | 0x01; data[3] = (pts3 & 0x7F80) >> 7; data[4] = ((pts3 & 0x007F) << 1) | 0x01; } /* * Does the given PES packet contain a PTS? * * - `packet` is the PES packet datastructure * * Returns TRUE if it does, FALSE if it does not (or is in error) */ extern int PES_packet_has_PTS(PES_packet_data_p packet) { byte *data = packet->data; byte stream_id; int packet_length; byte *bytes; int PTS_DTS_flags; if (data[0] != 0 || data[1] != 0 || data[2] != 1) { fprint_err("### PES_packet_has_PTS: " "PES packet start code prefix is %02x %02x %02x, not 00 00 01", data[0],data[1],data[2]); return FALSE; } stream_id = data[3]; packet_length = (data[4] << 8) | data[5]; bytes = data + 6; // if (packet_length == 0) // Elementary video data of unspecified length // return 0; if (packet_length == 0) packet_length = packet->data_len - 6; switch (stream_id) { case STREAM_ID_PROGRAM_STREAM_MAP: case STREAM_ID_PRIVATE_STREAM_2: case STREAM_ID_ECM_STREAM: case STREAM_ID_EMM_STREAM: case STREAM_ID_PROGRAM_STREAM_DIRECTORY: case STREAM_ID_DSMCC_STREAM: case STREAM_ID_H222_E_STREAM: return FALSE; // Just data bytes case STREAM_ID_PADDING_STREAM: return FALSE; // Just padding bytes default: break; // Some sort of data we might be interested in dissecting } if (IS_H222_PES(data)) { // It's H.222.0 PTS_DTS_flags = (bytes[1] & 0xC0) >> 6; } else { // We assume it's MPEG-1 // Note that the following duplicates code in calc_mpeg1_pes_offset, // since it wants to look partway through the data offset... int posn = 0; // Ignore any up-front padding bytes while (posn < packet_length && bytes[posn] == 0xFF) posn++; if (posn == packet_length) return FALSE; // no space for anything else if ((bytes[posn] & 0xC0) == 0x40) // ignore buffer scale/size posn += 2; if (posn == packet_length) return FALSE; // no space for PTS/DTS if ((bytes[posn] & 0xF0) == 0x20) // ignore PTS PTS_DTS_flags = 2; else if ((bytes[posn] & 0xF0) == 0x30) // ignore PTS and DTS PTS_DTS_flags = 3; else PTS_DTS_flags = 0; } return (PTS_DTS_flags == 2 || PTS_DTS_flags == 3); } /* * Report on the content of a PES packet - specifically, its header data. * * - `prefix` is a string to put before each line of output * - `data` is the packet data, and `data_len` its length * - `show_data` should be TRUE if the start of the data for each packet should * be shown * * Returns 0 if all went well, 1 if an error occurs. */ extern int report_PES_data_array(char *prefix, byte *data, int data_len, int show_data) { // This code was originally translated from the Python code in TS.py byte stream_id; int packet_length; byte *bytes; int err; uint64_t pts, dts; int got_pts = FALSE; // pessimistic int got_dts = FALSE; // pessimistic if (data[0] != 0 || data[1] != 0 || data[2] != 1) { fprint_err("### PES packet start code prefix is %02x %02x %02x, not 00 00 01", data[0],data[1],data[2]); return 1; } stream_id = data[3]; packet_length = (data[4] << 8) | data[5]; bytes = data + 6; // if (packet_length == 0) // Elementary video data of unspecified length // return 0; fprint_msg("%sPES packet: stream id %02x (",prefix,stream_id); print_stream_id(TRUE,stream_id); fprint_msg("), packet length %d",packet_length); if (packet_length == 0) { packet_length = data_len - 6; fprint_msg(" (actual length %d)",packet_length); } else if (packet_length != data_len - 6) { fprint_msg(" (actual length %d)",data_len - 6); } switch (stream_id) { case STREAM_ID_PROGRAM_STREAM_MAP: case STREAM_ID_PRIVATE_STREAM_2: case STREAM_ID_ECM_STREAM: case STREAM_ID_EMM_STREAM: case STREAM_ID_PROGRAM_STREAM_DIRECTORY: case STREAM_ID_DSMCC_STREAM: case STREAM_ID_H222_E_STREAM: print_msg("\n Just data bytes\n"); print_data(TRUE," ",bytes,packet_length,20); return 0; // Just data bytes case STREAM_ID_PADDING_STREAM: print_msg("\n"); return 0; // Just padding bytes default: break; // Some sort of data we might be interested in dissecting } if (IS_H222_PES(data)) { // Yes, it's H.222.0 int PES_scrambling_control; int PES_priority; int data_alignment_indicator; int copyright; int original_or_copy; int PTS_DTS_flags; int ESCR_flag; int ES_rate_flag; int DSM_trick_mode_flag; int additional_copy_info_flag; int PES_CRC_flag; int PES_extension_flag; int PES_header_data_length; print_msg("\n"); PES_scrambling_control = (bytes[0] & 0x30) >> 4; PES_priority = (bytes[0] & 0x08) >> 3; data_alignment_indicator = (bytes[0] & 0x04) >> 2; copyright = (bytes[0] & 0x02) >> 1; original_or_copy = bytes[0] & 0x01; fprint_msg("%s scrambling %d, priority %d, data %s, %s, %s\n", prefix, PES_scrambling_control, PES_priority, (data_alignment_indicator?"aligned":"unaligned"), (copyright?"copyrighted":"copyright undefined"), (original_or_copy?"original":"copy")); PTS_DTS_flags = (bytes[1] & 0xC0) >> 6; ESCR_flag = (bytes[1] & 0x20) >> 5; ES_rate_flag = (bytes[1] & 0x10) >> 4; DSM_trick_mode_flag = (bytes[1] & 0x08) >> 3; additional_copy_info_flag = (bytes[1] & 0x04) >> 2; PES_CRC_flag = (bytes[1] & 0x02) >> 1; PES_extension_flag = bytes[1] & 0x01; fprint_msg("%s %s, ESCR %d, ES_rate %d, DSM trick mode %d, additional copy" " info %d, PES CRC %d, PES extension %d\n", prefix, (PTS_DTS_flags==2?"PTS": PTS_DTS_flags==3?"PTS & DTS": PTS_DTS_flags==0?"no PTS/DTS":""), ESCR_flag,ES_rate_flag,DSM_trick_mode_flag, additional_copy_info_flag,PES_CRC_flag,PES_extension_flag); PES_header_data_length = bytes[2]; fprint_msg("%s PES header data length %d\n",prefix,PES_header_data_length); if (PTS_DTS_flags == 2) { err = decode_pts_dts(&bytes[3],2,&pts); if (err) return 1; got_pts = TRUE; } if (PTS_DTS_flags == 3) { err = decode_pts_dts(&bytes[3],3,&pts); if (err) return 1; got_pts = TRUE; err = decode_pts_dts(&bytes[8],1,&dts); if (err) return 1; got_dts = TRUE; } if (got_pts || got_dts) { fprint_msg("%s PTS " LLU_FORMAT,prefix,pts); if (got_dts) fprint_msg(", DTS " LLU_FORMAT,dts); print_msg("\n"); } if (show_data) { bytes += 3 + PES_header_data_length; if (prefix && strlen(prefix) > 0) fprint_msg("%s",prefix); print_data(TRUE," ",bytes,packet_length-3-PES_header_data_length,20); } } else { // We assume it's MPEG-1 int posn = 0; print_msg(" (MPEG-1)\n"); // Ignore any up-front padding bytes while (posn < packet_length && bytes[posn] == 0xFF) posn++; if (posn < packet_length) { if ((bytes[posn] & 0xC0) == 0x40) // ignore buffer scale/size posn += 2; if (posn == packet_length) return 0; // no space for PTS/DTS if ((bytes[posn] & 0xF0) == 0x20) // PTS { err = decode_pts_dts(&bytes[posn],2,&pts); if (err) return 1; got_pts = TRUE; posn += 5; } else if ((bytes[posn] & 0xF0) == 0x30) // PTS and DTS { err = decode_pts_dts(&bytes[posn],3,&pts); if (err) return 1; got_pts = TRUE; posn += 5; err = decode_pts_dts(&bytes[posn],1,&dts); if (err) return 1; got_dts = TRUE; posn += 5; } else if (bytes[posn] == 0x0F) // check for paranoia posn ++; else { fprint_err("### MPEG-1 PES packet has 0x%1xX" " instead of 0x40, 0x2X, 0x3X or 0x0F\n",(bytes[posn]&0xF0)>>4); posn ++; // what else can we do? } if (got_pts || got_dts) { fprint_msg("%s PTS " LLU_FORMAT,prefix,pts); if (got_dts) fprint_msg(", DTS " LLU_FORMAT,dts); print_msg("\n"); } if (show_data) { bytes += posn; if (prefix && strlen(prefix) > 0) fprint_msg("%s",prefix); print_data(TRUE," ",bytes,packet_length-posn,20); } } } return 0; } /* * Report on the content of a PES packet. * * This gives a longer form of report than report_PES_data_array(), and * can also present substream data for audio stream_types. * * - `stream_type` is the stream type of the data, or -1 if it is not * known (i.e., if this packet is from PS data). * - `payload` is the packet data. * - `payload_len` is the actual length of the payload (for a TS packet, * this will generally be less than the PES packet's length). * - if `show_data_len` is non-0 then the data for the PES packet will * also be shown, up to that length * * Returns 0 if all went well, 1 if something went wrong. */ extern void report_PES_data_array2(int stream_type, byte *payload, int payload_len, int show_data_len) { int err; int with_pts = FALSE; int with_dts = FALSE; uint64_t pts, dts; int PES_packet_length; byte *data = NULL; int data_len = 0; byte stream_id; if (payload_len == 0) { print_msg(" Payload has length 0\n"); return; } else if (payload == NULL) { fprint_msg(" Payload is NULL, but should be length %d\n",payload_len); return; } stream_id = payload[3]; PES_packet_length = (payload[4] << 8) | payload[5]; print_msg(" PES header\n"); fprint_msg(" Start code: %02x %02x %02x\n", payload[0],payload[1],payload[2]); fprint_msg(" Stream ID: %02x (%d) ",stream_id,stream_id); print_h262_start_code_str(stream_id); print_msg("\n"); fprint_msg(" PES packet length: %04x (%d)\n", PES_packet_length,PES_packet_length); if (IS_H222_PES(payload)) { // Looks like H.222.0 switch (stream_id) { case STREAM_ID_PROGRAM_STREAM_MAP: case STREAM_ID_PRIVATE_STREAM_2: case STREAM_ID_ECM_STREAM: case STREAM_ID_EMM_STREAM: case STREAM_ID_PROGRAM_STREAM_DIRECTORY: case STREAM_ID_DSMCC_STREAM: case STREAM_ID_H222_E_STREAM: print_msg(" Just data bytes\n"); print_data(TRUE," Data",payload+6,payload_len-6,1000); return; // Just data bytes case STREAM_ID_PADDING_STREAM: print_msg(" Padding stream\n"); return; // Just padding bytes default: break; // Some sort of data we might be interested in dissecting } fprint_msg(" Flags: %02x %02x",payload[6],payload[7]); if (payload[6] != 0) { int scramble = (payload[6] & 0x30) >> 8; if (scramble != 0) fprint_msg(" scramble-control %d",scramble); if (ON(payload[6],0x08)) print_msg(" PES-priority"); if (ON(payload[6],0x04)) print_msg(" data-aligned"); if (ON(payload[6],0x02)) print_msg(" copyright"); if (ON(payload[6],0x01)) print_msg(" original/copy"); } if (payload[7] != 0) { print_msg(" :"); if (ON(payload[7],0x80)) { with_pts = TRUE; print_msg(" PTS"); } if (ON(payload[7],0x40)) { with_dts = TRUE; print_msg(" DTS"); } if (ON(payload[7],0x20)) print_msg(" ESCR"); if (ON(payload[7],0x10)) print_msg(" ES-rate"); if (ON(payload[7],0x08)) print_msg(" DSM-trick-mode"); if (ON(payload[7],0x04)) print_msg(" more-copy-info"); if (ON(payload[7],0x02)) print_msg(" CRC"); if (ON(payload[7],0x01)) print_msg(" extension"); } print_msg("\n"); fprint_msg(" PES header len %d\n", payload[8]); if (with_pts) { err = decode_pts_dts(&(payload[9]),(with_dts?3:2),&pts); if (!err) fprint_msg(" PTS " LLU_FORMAT "\n",pts); } if (with_dts) { err = decode_pts_dts(&(payload[14]),1,&dts); if (!err) fprint_msg(" DTS " LLU_FORMAT "\n",dts); } data = payload + 9 + payload[8]; data_len = payload_len - 9 - payload[8]; // We know this is the start of a packet. If it is private_stream_1, // look to see if it is AC3 or DTS // If it is stream type 0x81, then do the same... // (maybe should do this for *any* of the 0x8N private streams?) if (stream_type == 0x06 || stream_type == 0x81) { if (data_len >= 2 && data[0] == 0x0B && data[1] == 0x77) print_msg(" AC-3 audio data\n"); else if (data_len >= 4 && data[0] == 0x7F && data[1] == 0xFE && data[1] == 0x80 && data[2] == 0x01) print_msg(" DTS audio data\n"); } } else { // We assume it's MPEG-1 int posn = 0; print_msg(" MPEG-1 packet layer packet\n"); if (stream_id != STREAM_ID_PRIVATE_STREAM_2) { // Skip any up-front padding bytes while (posn < PES_packet_length && payload[6+posn] == 0xFF) posn++; if (posn != 0) fprint_msg(" %d stuffing byte%s\n",posn,posn==1?"":"s"); if (posn < PES_packet_length) { if ((payload[6+posn] & 0xC0) == 0x40) { fprint_msg(" STD buffer scale %d\n",ON(payload[6+posn],5)); fprint_msg(" STD buffer size %d\n",(payload[6+posn] & 0x1F) << 8 | (payload[6+posn+1])); posn += 2; } if (posn == PES_packet_length) return; // no space for PTS/DTS if ((payload[6+posn] & 0xF0) == 0x20) // PTS { err = decode_pts_dts(&payload[6+posn],2,&pts); if (err) return; with_pts = TRUE; posn += 5; } else if ((payload[6+posn] & 0xF0) == 0x30) // PTS and DTS { err = decode_pts_dts(&payload[6+posn],3,&pts); if (err) return; with_pts = TRUE; posn += 5; err = decode_pts_dts(&payload[6+posn],1,&dts); if (err) return; with_dts = TRUE; posn += 5; } else if (payload[6+posn] == 0x0F) // check for paranoia posn ++; else { fprint_err("### MPEG-1 PES packet has 0x%1xX" " instead of 0x40, 0x2X, 0x3X or 0x0F\n",(payload[posn]&0xF0)>>4); posn ++; // what else can we do? } if (with_pts || with_dts) { fprint_msg(" PTS " LLU_FORMAT "\n",pts); if (with_dts) fprint_msg(" DTS " LLU_FORMAT "\n",dts); print_msg("\n"); } data = payload + 6 + posn; data_len = payload_len - 6 - posn; } } else { data = payload + 6; data_len = payload_len - 6; } } if (show_data_len) print_data(TRUE," Data",data,data_len,show_data_len); } /* * If the given PES packet data contains a PTS field, return it * * - `data` is the data for this PES packet * - `data_len` is its length * - `got_pts` is TRUE if a PTS field was found, in which case * - `pts` is that PTS value * * Returns 0 if all went well, 1 if an error occurs. */ extern int find_PTS_in_PES(byte data[], int32_t data_len, int *got_pts, uint64_t *pts) { byte stream_id; int packet_length; byte *bytes; int PTS_DTS_flags; *got_pts = FALSE; // pessimistic if (data[0] != 0 || data[1] != 0 || data[2] != 1) { fprint_err("### find_PTS_in_PES:" " PES packet start code prefix is %02x %02x %02x, not 00 00 01\n", data[0],data[1],data[2]); return 1; } stream_id = data[3]; packet_length = (data[4] << 8) | data[5]; // if (packet_length == 0) // Elementary video data of unspecified length // return 0; switch (stream_id) { case STREAM_ID_PROGRAM_STREAM_MAP: case STREAM_ID_PRIVATE_STREAM_2: case STREAM_ID_ECM_STREAM: case STREAM_ID_EMM_STREAM: case STREAM_ID_PROGRAM_STREAM_DIRECTORY: case STREAM_ID_DSMCC_STREAM: case STREAM_ID_H222_E_STREAM: return 0; // Just data bytes case STREAM_ID_PADDING_STREAM: return 0; // Just padding bytes default: break; // Some sort of data we might be interested in dissecting } bytes = data + 6; if (IS_H222_PES(data)) { // Yes, it's H.222.0 PTS_DTS_flags = (bytes[1] & 0xC0) >> 6; if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) { int err = decode_pts_dts(&bytes[3],PTS_DTS_flags,pts); if (err) return 1; *got_pts = TRUE; } } else { int posn = 0; int marker; // We assume it's MPEG-1 // Ignore any up-front padding bytes while (posn < packet_length && bytes[posn] == 0xFF) posn++; if (posn < packet_length) { if ((bytes[posn] & 0xC0) == 0x40) // ignore buffer scale/size posn += 2; if (posn == packet_length) return 0; // no space for PTS/DTS marker = (bytes[posn] & 0xF0) >> 4; if (marker == 2 || // PTS marker == 3) // PTS and DTS { int err = decode_pts_dts(&bytes[posn],marker,pts); if (err) return 1; *got_pts = TRUE; } } } return 0; } /* * If the given PES packet data contains a DTS field, return it * * - `data` is the data for this PES packet * - `data_len` is its length * - `got_dts` is TRUE if a DTS field was found, in which case * - `dts` is that DTS value * * Returns 0 if all went well, 1 if an error occurs. */ extern int find_DTS_in_PES(byte data[], int32_t data_len, int *got_dts, uint64_t *dts) { byte stream_id; int packet_length; byte *bytes; int PTS_DTS_flags; *got_dts = FALSE; // pessimistic if (data[0] != 0 || data[1] != 0 || data[2] != 1) { fprint_err("### find_DTS_in_PES:" " PES packet start code prefix is %02x %02x %02x, not 00 00 01\n", data[0],data[1],data[2]); return 1; } stream_id = data[3]; packet_length = (data[4] << 8) | data[5]; // if (packet_length == 0) // Elementary video data of unspecified length // return 0; switch (stream_id) { case STREAM_ID_PROGRAM_STREAM_MAP: case STREAM_ID_PRIVATE_STREAM_2: case STREAM_ID_ECM_STREAM: case STREAM_ID_EMM_STREAM: case STREAM_ID_PROGRAM_STREAM_DIRECTORY: case STREAM_ID_DSMCC_STREAM: case STREAM_ID_H222_E_STREAM: return 0; // Just data bytes case STREAM_ID_PADDING_STREAM: return 0; // Just padding bytes default: break; // Some sort of data we might be interested in dissecting } bytes = data + 6; if (IS_H222_PES(data)) { // Yes, it's H.222.0 PTS_DTS_flags = (bytes[1] & 0xC0) >> 6; if (PTS_DTS_flags == 3) { // err = decode_pts_dts(&bytes[3],3,&pts); int err = decode_pts_dts(&bytes[8],1,dts); if (err) return 1; *got_dts = TRUE; } } else { int posn = 0; // We assume it's MPEG-1 // Ignore any up-front padding bytes while (posn < packet_length && bytes[posn] == 0xFF) posn++; if (posn < packet_length) { if ((bytes[posn] & 0xC0) == 0x40) // ignore buffer scale/size posn += 2; if (posn == packet_length) return 0; // no space for PTS/DTS if ((bytes[posn] & 0xF0) == 0x30) // PTS and DTS { int err = decode_pts_dts(&bytes[posn+5],1,dts); if (err) return 1; *got_dts = TRUE; } } } return 0; } /* * If the given PES packet data contains a PTS and/or DTS field, return it * * - `data` is the data for this PES packet * - `data_len` is its length * - `got_pts` is TRUE if a PTS field was found, in which case * - `pts` is that PTS value * - `got_dts` is TRUE if a DTS field was found, in which case * - `dts` is that DTS value * * Returns 0 if all went well, 1 if an error occurs. */ extern int find_PTS_DTS_in_PES(byte data[], int32_t data_len, int *got_pts, uint64_t *pts, int *got_dts, uint64_t *dts) { byte stream_id; int packet_length; byte *bytes; int PTS_DTS_flags; *got_pts = FALSE; // pessimistic *got_dts = FALSE; if (data[0] != 0 || data[1] != 0 || data[2] != 1) { fprint_err("### find_PTS_DTS_in_PES" ": PES packet start code prefix is %02x %02x %02x, not 00 00 01\n", data[0],data[1],data[2]); return 1; } stream_id = data[3]; packet_length = (data[4] << 8) | data[5]; // if (packet_length == 0) // Elementary video data of unspecified length // return 0; switch (stream_id) { case STREAM_ID_PROGRAM_STREAM_MAP: case STREAM_ID_PRIVATE_STREAM_2: case STREAM_ID_ECM_STREAM: case STREAM_ID_EMM_STREAM: case STREAM_ID_PROGRAM_STREAM_DIRECTORY: case STREAM_ID_DSMCC_STREAM: case STREAM_ID_H222_E_STREAM: return 0; // Just data bytes case STREAM_ID_PADDING_STREAM: return 0; // Just padding bytes default: break; // Some sort of data we might be interested in dissecting } bytes = data + 6; if (IS_H222_PES(data)) { // Yes, it's H.222.0 PTS_DTS_flags = (bytes[1] & 0xC0) >> 6; if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) { int err = decode_pts_dts(&bytes[3],PTS_DTS_flags,pts); if (err) return 1; *got_pts = TRUE; } if (PTS_DTS_flags == 3) { // err = decode_pts_dts(&bytes[3],3,&pts); int err = decode_pts_dts(&bytes[8],1,dts); if (err) return 1; *got_dts = TRUE; } } else { int posn = 0; int marker; // We assume it's MPEG-1 // Ignore any up-front padding bytes while (posn < packet_length && bytes[posn] == 0xFF) posn++; if (posn < packet_length) { if ((bytes[posn] & 0xC0) == 0x40) // ignore buffer scale/size posn += 2; if (posn == packet_length) return 0; // no space for PTS/DTS marker = (bytes[posn] & 0xF0) >> 4; if (marker == 2 || // PTS marker == 3) // PTS and DTS { int err = decode_pts_dts(&bytes[posn],marker,pts); if (err) return 1; *got_pts = TRUE; } if (marker == 3) // PTS and DTS { int err = decode_pts_dts(&bytes[posn+5],1,dts); if (err) return 1; *got_dts = TRUE; } } } // If we have no DTS then it is the same as PTS if (*got_pts && !*got_dts) { *dts = *pts; *got_dts = TRUE; } return 0; } /* * If the given PES packet data contains an ESCR field, return it * * - `data` is the data for this PES packet * - `data_len` is its length * - `got_escr` is TRUE if an ESCR field was found, in which case * - `escr` is that ESCR value * * Returns 0 if all went well, 1 if an error occurs. */ extern int find_ESCR_in_PES(byte data[], int32_t data_len, int *got_escr, uint64_t *escr) { byte stream_id; // int packet_length; byte *bytes; *got_escr = FALSE; // pessimistic *escr = 0; if (data[0] != 0 || data[1] != 0 || data[2] != 1) { fprint_err("### find_ESCR_in_PES:" " PES packet start code prefix is %02x %02x %02x, not 00 00 01\n", data[0],data[1],data[2]); return 1; } stream_id = data[3]; // packet_length = (data[4] << 8) | data[5]; // if (packet_length == 0) // Elementary video data of unspecified length // return 0; switch (stream_id) { case STREAM_ID_PROGRAM_STREAM_MAP: case STREAM_ID_PRIVATE_STREAM_2: case STREAM_ID_ECM_STREAM: case STREAM_ID_EMM_STREAM: case STREAM_ID_PROGRAM_STREAM_DIRECTORY: case STREAM_ID_DSMCC_STREAM: case STREAM_ID_H222_E_STREAM: return 0; // Just data bytes case STREAM_ID_PADDING_STREAM: return 0; // Just padding bytes default: break; // Some sort of data we might be interested in dissecting } bytes = data + 6; if (IS_H222_PES(data)) // H.222.0 may have ESCR, MPEG-1 mayn't { // Yes, it's H.222.0 *got_escr = (bytes[1] & 0x20) == 0x20; if (*got_escr) { uint64_t ESCR_base; uint32_t ESCR_extn; int PTS_DTS_flags = (bytes[1] & 0xC0) >> 6; int offset; if (PTS_DTS_flags == 2) offset = 2 + 5; else if (PTS_DTS_flags == 3) offset = 2 + 10; else offset = 2 + 0; // or so we hope ESCR_base = (bytes[offset+4] >> 3) | (bytes[offset+3] << 5) | (bytes[offset+2] << 13) | (bytes[offset+1] << 20) | ((((uint64_t)bytes[offset]) & 0x03) << 28) | ((((uint64_t)bytes[offset]) & 0x38) << 27); ESCR_extn = (bytes[offset+5] >> 1) | (bytes[offset+4] << 7); *escr = ESCR_base * 300 + ESCR_extn; } } return 0; } // ============================================================ // Server support // ============================================================ /* * Packets can be written out to a client via a TS writer, as a * "side effect" of reading them. The original mechanism was to * write out PES packets (as TS) as they are read. This will work * for PS or TS data, and writes out only those PES packets that * have been read for video or audio data. * * An alternative, which will only work for TS input data, is * to write out TS packets as they are read. This will write all * TS packets to the client. * * - `reader` is our PES reader context * - `tswriter` is the TS writer * - if `write_PES`, then write PES packets out as they are read from * the input, otherwise write TS packets. * - `program_freq` is how often to write out program data (PAT/PMT) * if we are writing PES data (if we are writing TS data, then the * program data will be in the original TS packets) */ extern void set_server_output(PES_reader_p reader, TS_writer_p tswriter, int write_PES, int program_freq) { reader->tswriter = tswriter; reader->program_freq = program_freq; reader->program_index = 0; reader->write_PES_packets = write_PES; reader->write_TS_packets = !write_PES; reader->suppress_writing = FALSE; return; } /* * Start packets being written out to a TS writer (again). * * If packets were already being written out, this does nothing. * * If set_server_output() has not been called to define a TS writer * context, this will have no effect. * * If `reader` is NULL, nothing is done. */ extern void start_server_output(PES_reader_p reader) { if (reader != NULL) reader->suppress_writing = FALSE; return; } /* * Stop packets being written out to a TS writer. * * If packets were already not being written out, this does nothing. * * If `reader` is NULL, nothing is done. */ extern void stop_server_output(PES_reader_p reader) { if (reader != NULL) reader->suppress_writing = TRUE; return; } /* * When outputting PES packets in "normal play" mode, add ``extra`` PES * packets (of the same size as each real packet) to the output. This * makes the amount of data output be about ``extra``+1 times the amount * read (the discrepancy is due to any program data being written). * * This "expansion" or "padding" of the data can be useful for benchmarking * the recipient, as the extra data (which has an irrelevant stream id) * will be ignored by the video processor, but not by preceding systems. * * This does nothing if TS packets are being output directly. * * - `reader` is our PES reader context * - `extra` is how many extra packets to output per "real" packet. */ extern void set_server_padding(PES_reader_p reader, int extra) { reader->pes_padding = extra; return; } /* * Write out TS program data based on the information we have within the given * PES reader context (as amended by any calls of * `set_PES_reader_program_data`). * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int write_program_data(PES_reader_p reader, TS_writer_p output) { // We know we support at most two program streams for output int num_progs = 0; uint32_t prog_pids[2]; byte prog_type[2]; int err; uint32_t pcr_pid; // If we are writing out TS data as a side effect of reading TS when // assembling our PES packets, we should not write out any program // data ourselves, as it is (or should be) already in the TS data if (reader->write_TS_packets && !reader->suppress_writing) // should we care about suppression? return 0; // Of course, if we haven't *found* any program information yet, // there's not much we can do (even if the user is overriding the // program information for TS data, we still won't have worked out // exactly what we're doing until we've read the program information, // so this is probably still a sensible restriction) if (!reader->got_program_data) return 0; if (reader->is_TS) { // For TS, we can use the stream types from the PMT itself if (reader->video_pid != 0) { pmt_stream_p stream = pid_stream_in_pmt(reader->program_map, reader->video_pid); if (stream == NULL) { fprint_err("### Cannot find video PID %04x in program map\n", reader->video_pid); return 1; } prog_pids[0] = reader->output_video_pid; // may not be the same prog_type[0] = stream->stream_type; num_progs = 1; } if (reader->audio_pid != 0) { pmt_stream_p stream = pid_stream_in_pmt(reader->program_map, reader->audio_pid); if (stream == NULL) { fprint_err("### Cannot find audio PID %04x in program map\n", reader->audio_pid); return 1; } prog_pids[num_progs] = reader->output_audio_pid; // may not be the same prog_type[num_progs] = stream->stream_type; num_progs++; } } else { // For PS, we have to be given appropriate PIDs (which we'll assume the // user has done via the reader), and we need to deduce stream types from // the stream ids. // XXX For audio data, we can't yet tell what sort of audio we're reading, // so we'll make a (quiet possibly wrong) guess. num_progs = 1; prog_pids[0] = reader->output_video_pid; switch (reader->video_type) { case VIDEO_H264: prog_type[0] = AVC_VIDEO_STREAM_TYPE; break; case VIDEO_H262: prog_type[0] = MPEG2_VIDEO_STREAM_TYPE; break; case VIDEO_AVS: prog_type[0] = AVS_VIDEO_STREAM_TYPE; break; default: prog_type[0] = MPEG2_VIDEO_STREAM_TYPE; // what else to do? break; } prog_pids[1] = reader->output_audio_pid; if (reader->audio_stream_id == 0) { // The user has asked for (not private data) audio, but we haven't // found it yet. Make something sensible up... prog_type[1] = MPEG2_AUDIO_STREAM_TYPE; // a random guess } else { if (reader->audio_stream_id == PRIVATE1_AUDIO_STREAM_ID) prog_type[1] = reader->output_dolby_stream_type; else prog_type[1] = MPEG2_AUDIO_STREAM_TYPE; // a random guess } num_progs = 2; } pcr_pid = reader->output_pcr_pid; if (pcr_pid == 0) pcr_pid = reader->output_video_pid; #if SHOW_PROGRAM_INFO if (reader->give_info) { fprint_msg("PROGRAM %d: pmt %x (%d), pcr %x (%d)\n" " video %x (%d) type %02x (%s)\n", reader->output_program_number, reader->output_pmt_pid,reader->output_pmt_pid, pcr_pid,pcr_pid, reader->output_video_pid,reader->output_video_pid, prog_type[0],h222_stream_type_str(prog_type[0])); if (num_progs == 2) fprint_msg(" audio %x (%d) type %02x (%s)\n", reader->output_audio_pid,reader->output_audio_pid, prog_type[1],h222_stream_type_str(prog_type[1])); } #endif err = write_TS_program_data2(output, 1, // transport stream id reader->output_program_number, reader->output_pmt_pid,pcr_pid, num_progs,prog_pids,prog_type); if (err) { print_err("### Error writing out TS program data\n"); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pes_defns.h000066400000000000000000000243111261471605300170100ustar00rootroot00000000000000/* * Datastructures for reading PES packets from TS or PS files * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _pes_defns #define _pes_defns #include "compat.h" #include "pidint_defns.h" #include "ps_defns.h" #include "ts_defns.h" #include "tswrite_defns.h" // ------------------------------------------------------------ // A PES packet comes with some useful associated data struct PES_packet_data { byte *data; // The actual packet data int32_t data_len; // The length of the `data` array [1] int32_t length; // Its length offset_t posn; // The offset of its start in the file [2] int is_video; // Is this video data? (as opposed to audio) // For convenience, it's useful to be able to get at the PES packet's // "payload" (i.e., the ES data) as if it were a separate array. This // is, of course, just an offset into `data` byte *es_data; int32_t es_data_len; // The PES packet *does* tell us if its data starts with an ES packet // (i.e., if the 00 00 01 bytes come as the first bytes in the data), // so that's worth remembering int data_alignment_indicator; // Some applications want to know if a particular packet contains // a PTS or not int has_PTS; }; // [1] For PS data, data_len and length will always be the same. // For TS data, length is set when the first TS packet of the // PES packet is read, and data_len gradually increases to length // as "chunks" of the PES packet are read in // [2] For TS data, this is actually the offset of the first TS packet // containing the PES packet typedef struct PES_packet_data *PES_packet_data_p; #define SIZEOF_PES_PACKET_DATA sizeof(struct PES_packet_data) // ------------------------------------------------------------ // An expandable list of PID vs. PES packet data struct peslist { uint32_t *pid; // An array of the PIDs PES_packet_data_p *data; // An array of the corresponding PES data int length; // How many there are int size; // How big the arrays are }; typedef struct peslist *peslist_p; #define SIZEOF_PESLIST sizeof(struct peslist) #define PESLIST_START_SIZE 2 // Guess at one audio, one video #define PESLIST_INCREMENT 1 // And a very conservative extension policy // ------------------------------------------------------------ // A PES "reader" datastructure is the interface through which one reads // PES packets from a TS or PS file struct PES_reader { int is_TS; // Is it is TS (as opposed to PS)? // If it is TS, we read via a TS read-ahead buffer TS_reader_p tsreader; // If it is PS, we read via a PS read-ahead buffer PS_reader_p psreader; int give_info; // Should information messages be output? int give_warning; // Should warning messages be output (to stderr)? PES_packet_data_p packet; // The current PES packet // When reading PS packets, `posn` is the position of the current (or last) // PS or TS packet. offset_t posn; // For PS data, we need to know if it is H.264 (MPEG-4/AVC) or not int is_h264; // for backwards compatibility int video_type; // the actual (believed) video type // For PS and TS, we can choose to ignore audio entirely int video_only; // For PS, if we're not ignoring audio, we either look for a specific // audio stream id (specified by the user), or we will take the first // we find that is not Dolby. This latter is indicated by audio_stream // being set to 0 byte audio_stream_id; // If not, the stream id of the audio we want // When reading TS data, we need the program information to make sense // of what is going on int got_program_data; // Do we know our program data yet? pmt_p program_map; // The content of the (current/last) PMT // And from that, we can work out our video and audio (if any) pids, etc. uint32_t video_pid; // Zero if not yet known uint32_t audio_pid; // Ditto uint32_t pcr_pid; // A copy of the value from the PMT uint16_t program_number; // Which program are we reading? (0=first) uint32_t pmt_pid; // What's the PMT PID? // PMTs may be split over several TS packets, so we need a buffer // to build them in byte *pmt_data; // The buffer (NULL when not in use) int pmt_data_len; // The buffers length = the PMT section length + 3 int pmt_data_used; // How much of said data we've already got // In order to write out TS data, we also need program information. // Obviously, the simplest case is when reading TS and writing it out // again, with the same settings. However, we also have to cope with // reading in PS data (which has no TS program information), and writing // out TS data with *different* program information. // If we're reading TS data, the default is to use the program data we // find therein. If `override_program_data` is TRUE, then we ignore that, // and use the values given by the user instead. int override_program_data; // Regardless, the following are the values to use when writing TS data out: uint32_t output_video_pid; uint32_t output_audio_pid; uint32_t output_pcr_pid; uint16_t output_program_number; uint32_t output_pmt_pid; // If we're reading Dolby (AC-3) audio, then there are two choices for the // stream type. DVB uses stream type 0x06, and ATSC uses stream type 0x81. byte dolby_stream_type; // The Dolby stream type we read (if any) byte output_dolby_stream_type; int override_dolby_stream_type; // Override whatever we read // Before we can write out TS data, we need some basic program information. // This is read in automatically if the input is TS, and must be supplied // by the user (via set_PES_reader_program_data) if the input is PS. // When reading a TS file, more than one PES packet may be being built // at the same time. At any time, the "next" read PES packet will be the // first one to be completely read in peslist_p packets; // The packets currently being read // If we are reading TS, and a PES packet has a declared length of 0, // then it can only be ended by the *next* PES packet of the same PID // (or by EOF, of course). In this case, we want to return the newly // ended PES packet, and the *next* read request should continue // with the PES packet we hadn't yet finished with. However, it is // technically possible (although unlikely) that the new (just started) // PES packet will end in its first TS packet. In that case, we want // to return *it* next time we try to read a TS packet. To facilitate // that, we can remember it here... PES_packet_data_p deferred; // If we ended such a packet on EOF, it's moderately convenient to // remember that we had found EOF, rather than try to bump into it again int had_eof; // When being used by a server, we want PES packets to be written out // as a "side effect" of reading them in to analyse their contents. // Thus we provide: int write_PES_packets; // TRUE if to write them out to: TS_writer_p tswriter; // this TS writer context int program_freq; // how often to write PAT/PMT out int program_index; // how long since we last did so // Sometimes, for instance when going from fast forwards to normal playing, // we've already output the (end of) the current PES packet by hand, and // thus don't want the automated server mechanism to output it for us. // It's thus useful to have a flag indicating this (which will be unset // as soon as the current PES packet has, indeed, not been written out) // Since this is (definitely) an internal detail, it must be set explicitly. int dont_write_current_packet; // For benchmarking purposes (of the recipient), it can be useful to be able // to "pad out" the data we're sending, so that it is times as big. If // ``expand`` is greater than 0, then ``expand`` "dummy" PES packets (of the // same size as the real one) will be output for each real PES packet (but // with an irrelevant stream id). int pes_padding; // If the original data is TS, and we want to send *all* of said data // to the server, it is sensible to write the *TS packets* as a side // effect of reading, rather than the PES packets. Thus we also have int write_TS_packets; // Obviously, one assumes that we are not going to be doing both at // the same time (since they write through the same tswriter interface) // In either case, sometimes it is useful to suppress writing packets // out for a while int suppress_writing; // Debugging: if this is set, and the appropriate code is compiled into // pes.c (see DEBUG_READ_PACKETS), then report on each PES packet read // and written. Even if DEBUG_READ_PACKETS is not defined, some output // will be produced. int debug_read_packets; }; typedef struct PES_reader *PES_reader_p; #define SIZEOF_PES_READER sizeof(struct PES_reader) // Given the PES packet data (i.e., the data starting 00 00 01 // ), decide if this PES packet is MPEG-1 (11172-1) or // H.222.0 (13818-1) #define IS_H222_PES(data) ((data[6] & 0xC0) == 0x80) #endif // _pes_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pes_fns.h000066400000000000000000000515041261471605300165030ustar00rootroot00000000000000/* * Functions for reading PES packets from TS or PS files * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _pes_fns #define _pes_fns #include "pes_defns.h" #include "es_defns.h" /* * Free a PES packet datastructure * * - `data` is the PES packet datastructure, which will be freed, * and returned as NULL. */ extern void free_PES_packet_data(PES_packet_data_p *data); /* * Look at the start of a file to determine if it appears to be transport * stream. Rewinds the file when it is finished. * * The file is assumed to be Transport Stream if it starts with 0x47 as * the first byte, and 0x47 recurs at 188 byte intervals (in other words, * it appears to start with several TS packets). * * - `input` is the file to check * - `is_TS` is TRUE if it looks like TS, as described above. * * Returns 0 if all goes well, 1 if there was an error. */ extern int determine_if_TS_file(int input, int *is_TS); /* * Build a PES reader datastructure for PS data * * - `ps` is the Program Stream to read the PES data from * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the resulting PES reader * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_PS_PES_reader(PS_reader_p ps, int give_info, int give_warnings, PES_reader_p *reader); /* * Build a PES reader datastructure for TS data * * - `tsreader` is the Transport Stream to read the PES data from * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `program_number` is only used for TS data, and identifies which program * to read. If this is 0 then the first program encountered in the first PAT * will be read. * - `reader` is the resulting PES reader * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_TS_PES_reader(TS_reader_p tsreader, int give_info, int give_warnings, uint16_t program_number, PES_reader_p *reader); /* * Build a PES reader datastructure * * - `input` is the file to read the PES data from * - `is_TS` should be TRUE if the data is TS, FALSE if it is PS * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `program_number` is only used for TS data, and identifies which program * to read. If this is 0 then the first program encountered in the first PAT * will be read. * - `reader` is the resulting PES reader * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_PES_reader(int input, int is_TS, int give_info, int give_warnings, uint16_t program_number, PES_reader_p *reader); /* * Open a Transport Stream file for PES packet reading * * - `filename` is the name of the file to open. * - `program_number` identifies which program to read. If this is 0 * then the first program encountered in the first PAT will be read. * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). If information messages are requested, and the * program number is given as 0, the actual program number chosen will * be reported as well. * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the PES reader context corresponding to the newly * opened file. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int open_PES_reader_for_TS(char *filename, uint16_t program_number, int give_info, int give_warnings, PES_reader_p *reader); /* * Open a Program Stream file for PES packet reading * * - `filename` is the name of the file to open. * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the PES reader context corresponding to the newly * opened file. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int open_PES_reader_for_PS(char *filename, int give_info, int give_warnings, PES_reader_p *reader); /* * Open a Program Stream or Transport Stream file for PES packet reading * * - `filename` is the name of the file to open. * - `give_info` is TRUE if information about program data, etc., should be * output (to stdout). * - `give_warnings` is TRUE if warnings (starting with "!!!") should be * output (to stderr), FALSE if they should be suppressed. * - `reader` is the PES reader context corresponding to the newly * opened file. * * If the file is Transport Stream, then this is equivalent to a call * of:: * * err = open_PES_reader_for_TS(filename,0,give_info,give_warnings,&reader); * * i.e., the first program found is the program that will be read. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int open_PES_reader(char *filename, int give_info, int give_warnings, PES_reader_p *reader); /* * Tell the PES reader whether we only want video data * * - `video_only` should be TRUE if audio is to be ignored, FALSE * if audio should be retained. * * By default, the PES reader returns video data and a single audio * stream (taken from the first audio stream encountered). */ extern void set_PES_reader_video_only(PES_reader_p reader, int video_only); /* * Tell the PES reader which audio stream we want. * * By default, the PES reader returns video data and a single audio * stream (taken from the first audio stream encountered). * * - `reader` is the PES reader context * - `stream_number` is the number of the audio stream to read, from * 0 to 31 (0x1F). * * This call only has effect if the input data is PS. * * Returns 0 if all went well, or 1 if there was an error (specifically, * that `stream_number` was not in the range 0-31). */ extern int set_PES_reader_audio_stream(PES_reader_p reader, int stream_number); /* * Tell the PES reader to get its audio stream from private stream 1 * (this is the stream that is conventionally used for Dolby in DVD data). * * By default, the PES reader returns video data and a single audio * stream (taken from the first audio stream encountered). * * - `reader` is the PES reader context * * This call only has effect if the input data is PS. */ extern void set_PES_reader_audio_private1(PES_reader_p reader); /* * Tell the PES reader to "pretend" it has read a PAT and PMT with * the given program information. * * This is intended for use in setting up sensible values when reading * PS data (which does not contain such information). It will silently * do nothing for TS data. * * Note that calling it more than once is allowed - it will happily * overwrite any previous values. * * - `reader` is the PES reader context * - `program_number` is the program number to assume. If this is 0, * then 1 will be used. * - `pmt_pid` is the PID for the PMT we've pretended to read. * - `video_pid` is the PID to assume for the video data * - `audio_pid` is the PID to assume for the audio data (if any) * - `pcr_pid` is the PID to assume for the PCR data - this will often * be the same as the `video_pid` */ extern void set_PES_reader_program_data(PES_reader_p reader, uint16_t program_number, uint32_t pmt_pid, uint32_t video_pid, uint32_t audio_pid, uint32_t pcr_pid); /* * Tell the PES reader that the PS data it is reading is MPEG-4/AVC, * as opposed to MPEG-1/MPEG-2. */ extern void set_PES_reader_h264(PES_reader_p reader); /* * Tell the PES reader that the PS data it is reading is of * type `video_type` (which is assumed to be a legitimate value * such as VIDEO_H264, etc.) */ extern void set_PES_reader_video_type(PES_reader_p reader, int video_type); /* * Tell the PES reader whether to output any Dolby (AC-3) audio data * it may read using the DVB stream type (0x06) or the ATSC stream * type (0x81). * * If it is reading TS data, then the default is to use whatever stream type * the Dolby audio was read with. * * If it is reading PS data, then the default is to assume DVB data. * * This call only has effect if Dolby audio data is actually selected. */ extern void set_PES_reader_dolby_stream_type(PES_reader_p reader, int is_dvb); /* * Reposition the PES reader to an earlier packet * * It is the caller's responsibility to choose a sensible `posn` to seek to. * * Note that using this to reposition in a PES reader does not affect any * "higher" reading context using this PES reader - specifically, if data * is being read via an ES reader, then calling this function directly * will result in the ES reader losing its positional information. * * In this case, `seek_ES` should be called. * * - `reader` is the PES reader context * - `posn` is a packet position obtained from an earlier PES packet * datastructure (this should *not* be a random offset in the input * file, as that will not in general work). * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int set_PES_reader_position(PES_reader_p reader, offset_t posn); /* * Free a PES reader, and the relevant datastructures. Does not close * the underlying file. * * - `reader` is the PES reader context. This will be freed, and * returned as NULL. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int free_PES_reader(PES_reader_p *reader); /* * Close a PES reader, and free the relevant datastructures. * * - `reader` is the PES reader context. This will be freed, and * returned as NULL. * * Returns 0 if all goes well, 1 if something goes wrong with closing the * file (although in that case, the `reader` will still have been freed). */ extern int close_PES_reader(PES_reader_p *reader); /* * Return the next PES packet from the input file * * - `reader` is a PES reader context * * Returns 0 if all goes well, EOF if end of file is read, and 1 if * something goes wrong. */ extern int read_next_PES_packet(PES_reader_p reader); /* * Given an MPEG-1 PES packet, determine the offset of the ES data. * * - `data` is the PES packet data, starting "00 00 01 * " * - `data_len` is the actual length of the data therein * * Returns the required offset (i.e., packet[offset] is the first byte * of the ES data within the PES packet). */ extern int calc_mpeg1_pes_offset(byte *data, int data_len); /* * Read in the next PES packet that contains ES data we are interested in * Ignores non-video packets. * * - `reader` is a PES reader context * * Returns 0 if all goes well, EOF if end of file is read, and 1 if * something goes wrong. */ extern int read_next_PES_ES_packet(PES_reader_p reader); /* * If the given PES packet data contains a PTS field, return it * * - `data` is the data for this PES packet * - `data_len` is its length * - `got_pts` is TRUE if a PTS field was found, in which case * - `pts` is that PTS value * * Returns 0 if all went well, 1 if an error occurs. */ extern int find_PTS_in_PES(byte data[], int32_t data_len, int *got_pts, uint64_t *pts); /* * If the given PES packet data contains a DTS field, return it * * - `data` is the data for this PES packet * - `data_len` is its length * - `got_dts` is TRUE if a DTS field was found, in which case * - `dts` is that DTS value * * Returns 0 if all went well, 1 if an error occurs. */ extern int find_DTS_in_PES(byte data[], int32_t data_len, int *got_dts, uint64_t *dts); /* * If the given PES packet data contains a PTS and/or DTS field, return it * * - `data` is the data for this PES packet * - `data_len` is its length * - `got_pts` is TRUE if a PTS field was found, in which case * - `pts` is that PTS value * - `got_dts` is TRUE if a DTS field was found, in which case * - `dts` is that DTS value * * Returns 0 if all went well, 1 if an error occurs. */ extern int find_PTS_DTS_in_PES(byte data[], int32_t data_len, int *got_pts, uint64_t *pts, int *got_dts, uint64_t *dts); /* * If the given PES packet data contains an ESCR field, return it * * - `data` is the data for this PES packet * - `data_len` is its length * - `got_escr` is TRUE if an ESCR field was found, in which case * - `escr` is that ESCR value * * Returns 0 if all went well, 1 if an error occurs. */ extern int find_ESCR_in_PES(byte data[], int32_t data_len, int *got_escr, uint64_t *escr); /* * Decode a PTS or DTS value. * * - `bytes` is the 5 bytes containing the encoded PTS or DTS value * - `required_guard` should be 2 for a PTS alone, 3 for a PTS before * a DTS, or 1 for a DTS after a PTS * - `value` is the PTS or DTS value as decoded * * Returns 0 if the PTS/DTS value is decoded successfully, 1 if an error occurs */ extern int decode_pts_dts(byte data[], int required_guard, uint64_t *value); /* * Encode a PTS or DTS. * * - `data` is the array of 5 bytes into which to encode the PTS/DTS * - `guard_bits` are the required guard bits: 2 for a PTS alone, 3 for * a PTS before a DTS, or 1 for a DTS after a PTS * - `value` is the PTS or DTS value to be encoded */ extern void encode_pts_dts(byte data[], int guard_bits, uint64_t value); /* * Does the given PES packet contain a PTS? * * - `packet` is the PES packet datastructure * * Returns TRUE if it does, FALSE if it does not (or is in error) */ extern int PES_packet_has_PTS(PES_packet_data_p packet); /* * Report on the content of a PES packet - specifically, its header data. * * - `prefix` is a string to put before each line of output * - `data` is the packet data, and `data_len` its length * - `show_data` should be TRUE if the start of the data for each packet should * be shown * * Returns 0 if all went well, 1 if an error occurs. */ extern int report_PES_data_array(char *prefix, byte *data, int data_len, int show_data); /* * Report on the content of a PES packet. * * This gives a longer form of report than report_PES_data_array(), and * can also present substream data for audio stream_types. * * - `stream_type` is the stream type of the data, or -1 if it is not * known (i.e., if this packet is from PS data). * - `payload` is the packet data. * - `payload_len` is the actual length of the payload (for a TS packet, * this will generally be less than the PES packet's length). * - if `show_data_len` is non-0 then the data for the PES packet will * also be shown, up to that length * * Returns 0 if all went well, 1 if something went wrong. */ extern void report_PES_data_array2(int stream_type, byte *payload, int payload_len, int show_data_len); // ============================================================ // Server support // ============================================================ /* * Packets can be written out to a client via a TS writer, as a * "side effect" of reading them. The original mechanism was to * write out PES packets (as TS) as they are read. This will work * for PS or TS data, and writes out only those PES packets that * have been read for video or audio data. * * An alternative, which will only work for TS input data, is * to write out TS packets as they are read. This will write all * TS packets to the client. * * - `reader` is our PES reader context * - `tswriter` is the TS writer * - if `write_PES`, then write PES packets out as they are read from * the input, otherwise write TS packets. * - `program_freq` is how often to write out program data (PAT/PMT) * if we are writing PES data (if we are writing TS data, then the * program data will be in the original TS packets) */ extern void set_server_output(PES_reader_p reader, TS_writer_p tswriter, int write_PES, int program_freq); /* * Start packets being written out to a TS writer (again). * * If packets were already being written out, this does nothing. * * If set_server_output() has not been called to define a TS writer * context, this will have no effect. * * If `reader` is NULL, nothing is done. */ extern void start_server_output(PES_reader_p reader); /* * Stop packets being written out to a TS writer. * * If packets were already not being written out, this does nothing. * * If set_server_output() has not been called to define a TS writer * context, this will have no effect. * * If `reader` is NULL, nothing is done. */ extern void stop_server_output(PES_reader_p reader); /* * When outputting PES packets in "normal play" mode, add ``extra`` PES * packets (of the same size as each real packet) to the output. This * makes the amount of data output be about ``extra``+1 times the amount * read (the discrepancy is due to any program data being written). * * This "expansion" or "padding" of the data can be useful for benchmarking * the recipient, as the extra data (which has an irrelevant stream id) * will be ignored by the video processor, but not by preceding systems. * * This does nothing if TS packets are being output directly. * * - `reader` is our PES reader context * - `extra` is how many extra packets to output per "real" packet. */ extern void set_server_padding(PES_reader_p reader, int extra); /* * Write out TS program data based on the information we have within the given * PES reader context (as amended by any calls of * `set_PES_reader_program_data`). * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int write_program_data(PES_reader_p reader, TS_writer_p output); #endif // _pes_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pidint.c000066400000000000000000000507251261471605300163340ustar00rootroot00000000000000/* * Support for lists (actually arrays) of PID versus integer * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include "compat.h" #include "pidint_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "ts_fns.h" #include "h222_defns.h" // ============================================================================ // PIDINT LIST maintenance // ============================================================================ /* * Initialise a new pid/int list datastructure. */ extern int init_pidint_list(pidint_list_p list) { list->length = 0; list->size = PIDINT_LIST_START_SIZE; list->number = malloc(sizeof(int)*PIDINT_LIST_START_SIZE); if (list->number == NULL) { print_err("### Unable to allocate array in program list datastructure\n"); return 1; } list->pid = malloc(sizeof(uint32_t)*PIDINT_LIST_START_SIZE); if (list->pid == NULL) { free(list->number); print_err("### Unable to allocate array in program list datastructure\n"); return 1; } return 0; } /* * Build a new pid/int list datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_pidint_list(pidint_list_p *list) { pidint_list_p new = malloc(SIZEOF_PIDINT_LIST); if (new == NULL) { print_err("### Unable to allocate pid/int list datastructure\n"); return 1; } if (init_pidint_list(new)) return 1; *list = new; return 0; } /* * Add a pid/integer pair to the end of the list * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int append_to_pidint_list(pidint_list_p list, uint32_t pid, int program) { if (list == NULL) { print_err("### Unable to append to NULL pid/int list\n"); return 1; } if (list->length == list->size) { int newsize = list->size + PIDINT_LIST_INCREMENT; list->number = realloc(list->number,newsize*sizeof(int)); if (list->number == NULL) { print_err("### Unable to extend pid/int list array\n"); return 1; } list->pid = realloc(list->pid,newsize*sizeof(uint32_t)); if (list->pid == NULL) { print_err("### Unable to extend pid/int list array\n"); return 1; } list->size = newsize; } list->number[list->length] = program; list->pid[list->length] = pid; list->length++; return 0; } /* * Remove a pid/integer pair from the list * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remove_from_pidint_list(pidint_list_p list, uint32_t pid) { int index; int ii; if (list == NULL) { print_err("### Unable to remove entry from NULL pid/int list\n"); return 1; } index = pid_index_in_pidint_list(list,pid); if (index == -1) { fprint_err("### Cannot remove PID %04x from pid/int list" " - it is not there\n",pid); return 1; } for (ii = index; ii < (list->length - 1); ii++) { list->pid[ii] = list->pid[ii+1]; list->number[ii] = list->number[ii+1]; } (list->length) --; return 0; } /* * Tidy up and free a pid/int list datastructure after we've finished with it * * Clears the datastructure, frees it and returns `list` as NULL. * * Does nothing if `list` is already NULL. */ extern void free_pidint_list(pidint_list_p *list) { if (*list == NULL) return; if ((*list)->number != NULL) { free((*list)->number); (*list)->number = NULL; } if ((*list)->pid != NULL) { free((*list)->pid); (*list)->pid = NULL; } (*list)->length = 0; (*list)->size = 0; free(*list); *list = NULL; } /* * Report on a pid/int list's contents */ extern void report_pidint_list(pidint_list_p list, char *list_name, char *int_name, int pid_first) { if (list == NULL) fprint_msg("%s is NULL\n",list_name); else if (list->length == 0) fprint_msg("%s is empty\n",list_name); else { int ii; fprint_msg("%s:\n",list_name); for (ii=0; iilength; ii++) { if (pid_first) fprint_msg(" PID %04x (%d) -> %s %d\n", list->pid[ii],list->pid[ii],int_name,list->number[ii]); else fprint_msg(" %s %d -> PID %04x (%d)\n", int_name,list->number[ii],list->pid[ii],list->pid[ii]); } } } /* * Lookup a PID to find its index in a pid/int list. * * Note that if `list` is NULL, then -1 will be returned - this is to * allow the caller to make a query before they have read a list from the * bitstream. * * Returns its index (0 or more) if the PID is in the list, -1 if it is not. */ extern int pid_index_in_pidint_list(pidint_list_p list, uint32_t pid) { int ii; if (list == NULL) return -1; for (ii = 0; ii < list->length; ii++) { if (list->pid[ii] == pid) return ii; } return -1; } /* * Lookup a PID to find the corresponding integer value in a pid/int list. * * Returns 0 if the PID is in the list, -1 if it is not. */ extern int pid_int_in_pidint_list(pidint_list_p list, uint32_t pid, int *number) { int ii; if (list == NULL) return -1; for (ii = 0; ii < list->length; ii++) { if (list->pid[ii] == pid) { *number = list->number[ii]; return 0; } } return -1; } /* * Lookup a PID to see if it is in a pid/int list. * * Note that if `list` is NULL, then FALSE will be returned - this is to * allow the caller to make a query before they have read a list from the * bitstream. * * Returns TRUE if the PID is in the list, FALSE if it is not. */ extern int pid_in_pidint_list(pidint_list_p list, uint32_t pid) { return pid_index_in_pidint_list(list,pid) != -1; } /* * Check if two pid/int lists have the same content. * * Note that: * * - a list always compares as the same as itself * - two NULL lists compare as the same * - the *order* of PID/int pairs in the lists does not matter * * Returns TRUE if the two have the same content, FALSE otherwise. */ extern int same_pidint_list(pidint_list_p list1, pidint_list_p list2) { int ii; if (list1 == list2) return TRUE; else if (list1 == NULL || list2 == NULL) return FALSE; else if (list1->length != list2->length) return FALSE; for (ii = 0; ii < list1->length; ii++) { uint32_t pid = list1->pid[ii]; int idx = pid_index_in_pidint_list(list2,pid); if (idx == -1) return FALSE; else if (list1->number[ii] != list2->number[idx]) return FALSE; } return TRUE; } /* * Report on a program stream list (a specialisation of report_pidint_list). * * - `list` is the stream list to report on * - `prefix` is NULL or a string to put before each line printed */ extern void report_stream_list(pidint_list_p list, char *prefix) { if (prefix!=NULL) print_msg(prefix); if (list == NULL) print_msg("Program stream list is NULL\n"); else if (list->length == 0) print_msg("Program stream list is empty\n"); else { int ii; print_msg("Program streams:\n"); for (ii=0; iilength; ii++) { if (prefix!=NULL) print_msg(prefix); fprint_msg(" PID %04x (%d) -> Stream type %3d (%s)\n", list->pid[ii],list->pid[ii],list->number[ii], h222_stream_type_str(list->number[ii])); } } } // ============================================================================ // PMT data maintenance // ============================================================================ /* * Initialise a PMT datastructure's stream lists */ static int init_pmt_streams(pmt_p pmt) { pmt->num_streams = 0; pmt->streams_size = PMT_STREAMS_START_SIZE; pmt->streams = malloc(SIZEOF_PMT_STREAM*PMT_STREAMS_START_SIZE); if (pmt->streams == NULL) { print_err("### Unable to allocate streams in PMT datastructure\n"); return 1; } return 0; } /* * Build a new PMT datastructure. * * `version_number` should be in the range 0-31, and will be treated as a * number modulo 32 if it is not. * * `PCR_pid` should be a legitimate PCR PID - i.e., in the range 0x0010 to * 0x1FFE, or 0x1FFF to indicate "unset". However, for convenience, the * value 0 will also be accepted, and converted to 0x1FFF. * * Returns (a pointer to) the new PMT datastructure, or NULL if some error * occurs. */ extern pmt_p build_pmt(uint16_t program_number, byte version_number, uint32_t PCR_pid) { pmt_p new; if (version_number > 31) version_number = version_number % 32; if (PCR_pid == 0) PCR_pid = 0x1FFF; // unset if (PCR_pid != 0x1FFF && (PCR_pid < 0x0010 || PCR_pid > 0x1ffe)) { fprint_err("### Error building PMT datastructure\n" " PCR PID %04x is outside legal program stream range\n", PCR_pid); return NULL; } new = malloc(SIZEOF_PMT); if (new == NULL) { print_err("### Unable to allocate PMT datastructure\n"); return NULL; } new->program_number = program_number; new->version_number = version_number; new->PCR_pid = PCR_pid; new->program_info_length = 0; new->program_info = NULL; if (init_pmt_streams(new)) { free(new); return NULL; } return new; } /* * Set the descriptor data on a PMT. Specifically, 'program info', * the descriptor data in the PMT "as a whole". * * Any previous program information for this PMT is lost. * * A copy of the program information bytes is taken. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int set_pmt_program_info(pmt_p pmt, uint16_t program_info_length, byte *program_info) { if (program_info_length > PMT_MAX_INFO_LENGTH) { fprint_err("### Program info length %d is more than %d\n", program_info_length,PMT_MAX_INFO_LENGTH); return 1; } if (pmt->program_info == NULL) { pmt->program_info = malloc(program_info_length); if (pmt->program_info == NULL) { print_err("### Unable to allocate program info in PMT datastructure\n"); return 1; } } else if (program_info_length != pmt->program_info_length) { // well, we might be shrinking it rather than growing it, but still pmt->program_info = realloc(pmt->program_info,program_info_length); if (pmt->program_info == NULL) { print_err("### Unable to extend program info in PMT datastructure\n"); return 1; } } memcpy(pmt->program_info,program_info,program_info_length); pmt->program_info_length = program_info_length; return 0; } /* * Add a program stream to a PMT datastructure * * If `ES_info_length` is greater than 0, then `ES_info` is copied. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int add_stream_to_pmt(pmt_p pmt, uint32_t elementary_PID, byte stream_type, uint16_t ES_info_length, byte *ES_info) { if (pmt == NULL) { print_err("### Unable to append to NULL PMT datastructure\n"); return 1; } if (elementary_PID < 0x0010 || elementary_PID > 0x1ffe) { fprint_err("### Error adding stream to PMT\n" " Elementary PID %04x is outside legal program stream range\n", elementary_PID); return 1; } if (ES_info_length > PMT_MAX_INFO_LENGTH) { fprint_err("### ES info length %d is more than %d\n", ES_info_length,PMT_MAX_INFO_LENGTH); return 1; } if (pmt->num_streams == pmt->streams_size) { int newsize = pmt->streams_size + PMT_STREAMS_INCREMENT; pmt->streams = realloc(pmt->streams,newsize*SIZEOF_PMT_STREAM); if (pmt->streams == NULL) { print_err("### Unable to extend PMT streams array\n"); return 1; } pmt->streams_size = newsize; } pmt->streams[pmt->num_streams].stream_type = stream_type; pmt->streams[pmt->num_streams].elementary_PID = elementary_PID; pmt->streams[pmt->num_streams].ES_info_length = ES_info_length; if (ES_info_length > 0) { pmt->streams[pmt->num_streams].ES_info = malloc(ES_info_length); if (pmt->streams[pmt->num_streams].ES_info == NULL) { print_err("### Unable to allocate PMT stream ES info\n"); return 1; } memcpy(pmt->streams[pmt->num_streams].ES_info,ES_info,ES_info_length); } else pmt->streams[pmt->num_streams].ES_info = NULL; pmt->num_streams++; return 0; } /* * Free a PMT stream datastructure */ static void free_pmt_stream(pmt_stream_p stream) { if (stream == NULL) return; if (stream->ES_info != NULL) { free(stream->ES_info); stream->ES_info = NULL; } } /* * Remove a program stream from a PMT. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remove_stream_from_pmt(pmt_p pmt, uint32_t pid) { int index; int ii; if (pmt == NULL) { print_err("### Unable to remove entry from NULL PMT datastructure\n"); return 1; } index = pid_index_in_pmt(pmt,pid); if (index == -1) { fprint_err("### Cannot remove PID %04x from PMT datastructure" " - it is not there\n",pid); return 1; } free_pmt_stream(&pmt->streams[index]); for (ii = index; ii < (pmt->num_streams - 1); ii++) pmt->streams[ii] = pmt->streams[ii+1]; (pmt->num_streams) --; return 0; } /* * Tidy up and free a PMT datastructure after we've finished with it * * Clears the datastructure, frees it and returns `pmt` as NULL. * * Does nothing if `pmt` is already NULL. */ extern void free_pmt(pmt_p *pmt) { if (*pmt == NULL) return; if ((*pmt)->num_streams > 0) { int ii; for (ii = 0; ii < (*pmt)->num_streams; ii++) free_pmt_stream(&(*pmt)->streams[ii]); (*pmt)->num_streams = 0; } if ((*pmt)->program_info != NULL) { free((*pmt)->program_info); (*pmt)->program_info = NULL; } free((*pmt)->streams); (*pmt)->program_info_length = 0; free(*pmt); *pmt = NULL; } /* * Lookup a PID to find its index in a PMT datastructure. * * Note that if `pmt` is NULL, then -1 will be returned. * * Returns its index (0 or more) if the PID is in the list, -1 if it is not. */ extern int pid_index_in_pmt(pmt_p pmt, uint32_t pid) { int ii; if (pmt == NULL) return -1; for (ii = 0; ii < pmt->num_streams; ii++) { if (pmt->streams[ii].elementary_PID == pid) return ii; } return -1; } /* * Lookup a PID to find the corresponding program stream information. * * Returns a pointer to the stream information if the PID is in the list, * NULL if it is not. */ extern pmt_stream_p pid_stream_in_pmt(pmt_p pmt, uint32_t pid) { int ii; if (pmt == NULL) return NULL; for (ii = 0; ii < pmt->num_streams; ii++) { if (pmt->streams[ii].elementary_PID == pid) return &pmt->streams[ii]; } return NULL; } /* * Lookup a PID to see if it is in a PMT datastructure. * * Note that if `pmt` is NULL, then FALSE will be returned. * * Returns TRUE if the PID is in the PMT's stream list, FALSE if it is not. */ extern int pid_in_pmt(pmt_p pmt, uint32_t pid) { return pid_index_in_pmt(pmt,pid) != -1; } /* * Check if two PMT streams have the same content. * * Returns TRUE if the two have the same content, FALSE otherwise. */ static int same_pmt_stream(pmt_stream_p str1, pmt_stream_p str2) { if (str1 == str2) // !!! return TRUE; else if (str1 == NULL || str2 == NULL) // !!! return FALSE; else if (str1->elementary_PID != str2->elementary_PID) return FALSE; else if (str1->ES_info_length != str2->ES_info_length) return FALSE; else if (memcmp(str1->ES_info,str2->ES_info,str1->ES_info_length)) return FALSE; else return TRUE; } /* * Check if two PMT datastructures have the same content. * * Note that: * * - a PMT datastructure always compares as the same as itself * - two NULL datastructures compare as the same * - a different version number means a different PMT * - the *order* of program streams in the PMTs does not matter * - descriptors must be identical as well, and byte order therein * does matter (this may need changing later on) * * Returns TRUE if the two have the same content, FALSE otherwise. */ extern int same_pmt(pmt_p pmt1, pmt_p pmt2) { int ii; if (pmt1 == pmt2) return TRUE; else if (pmt1 == NULL || pmt2 == NULL) return FALSE; else if (pmt1->PCR_pid != pmt2->PCR_pid) return FALSE; else if (pmt1->version_number != pmt2->version_number) return FALSE; else if (pmt1->program_info_length != pmt2->program_info_length) return FALSE; else if (pmt1->num_streams != pmt2->num_streams) return FALSE; else if (memcmp(pmt1->program_info,pmt2->program_info, pmt1->program_info_length)) return FALSE; for (ii = 0; ii < pmt1->num_streams; ii++) { uint32_t pid = pmt1->streams[ii].elementary_PID; int idx = pid_index_in_pmt(pmt2,pid); if (idx == -1) return FALSE; else if (!same_pmt_stream(&pmt1->streams[ii],&pmt2->streams[idx])) return FALSE; } return TRUE; } /* * Report on a PMT datastructure. * * - if `is_msg`, report as a message, otherwise as an error * - `prefix` is NULL or a string to put before each line printed * - `pmt` is the PMT to report on */ extern void report_pmt(int is_msg, char *prefix, pmt_p pmt) { if (prefix!=NULL) fprint_msg_or_err(is_msg,prefix); if (pmt == NULL) { fprint_msg_or_err(is_msg,"PMT is NULL\n"); return; } else fprint_msg_or_err(is_msg,"Program %d, version %d, PCR PID %04x (%d)\n", pmt->program_number,pmt->version_number,pmt->PCR_pid,pmt->PCR_pid); if (pmt->program_info_length > 0) { if (prefix!=NULL) fprint_msg_or_err(is_msg,prefix); print_data(is_msg," Program info",pmt->program_info, pmt->program_info_length,pmt->program_info_length); print_descriptors(is_msg,prefix," ",pmt->program_info, pmt->program_info_length); } if (pmt->num_streams > 0) { int ii; if (prefix!=NULL) fprint_msg_or_err(is_msg,prefix); fprint_msg_or_err(is_msg,"Program streams:\n"); for (ii=0; iinum_streams; ii++) { if (prefix!=NULL) fprint_msg_or_err(is_msg,prefix); fprint_msg_or_err(is_msg," PID %04x (%4d) -> Stream type %02x (%3d) %s\n", pmt->streams[ii].elementary_PID, pmt->streams[ii].elementary_PID, pmt->streams[ii].stream_type, pmt->streams[ii].stream_type, h222_stream_type_str(pmt->streams[ii].stream_type)); if (pmt->streams[ii].ES_info_length > 0) { if (prefix!=NULL) fprint_msg_or_err(is_msg,prefix); print_data(is_msg," ES info", pmt->streams[ii].ES_info, pmt->streams[ii].ES_info_length, pmt->streams[ii].ES_info_length); print_descriptors(is_msg,prefix," ", pmt->streams[ii].ES_info, pmt->streams[ii].ES_info_length); } } } } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pidint_defns.h000066400000000000000000000056371261471605300175220ustar00rootroot00000000000000/* * Datastructures for working PID/integer lists * * The PAT is adequately represented by a transport_stream_id and a * pidint_list of its program_number->PID mappings. * * A PMT requires a bit more structure, mainly to allow for the handling * of descriptor information. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _pidint_defns #define _pidint_defns #include "compat.h" // ---------------------------------------------------------------------------- // An expandable list of PID vs. integer struct pidint_list { int *number; // The integers uint32_t *pid; // The corresponding PIDs int length; // How many there are int size; // How big the arrays are }; typedef struct pidint_list *pidint_list_p; #define SIZEOF_PIDINT_LIST sizeof(struct pidint_list) #define PIDINT_LIST_START_SIZE 5 #define PIDINT_LIST_INCREMENT 10 // ---------------------------------------------------------------------------- // PMT - a representation of a Program Map Table struct _pmt_stream { byte stream_type; uint32_t elementary_PID; uint16_t ES_info_length; byte *ES_info; // the descriptor data therefor }; typedef struct _pmt_stream *pmt_stream_p; #define SIZEOF_PMT_STREAM sizeof(struct _pmt_stream) struct _pmt { uint16_t program_number; byte version_number; // perhaps not strictly necessary uint32_t PCR_pid; uint16_t program_info_length; byte *program_info; // the descriptor data therefor int streams_size; // the size of the `streams` array int num_streams; // the number of streams we know about pmt_stream_p streams; }; typedef struct _pmt *pmt_p; #define SIZEOF_PMT sizeof(struct _pmt) #define PMT_STREAMS_START_SIZE 5 #define PMT_STREAMS_INCREMENT 10 #define PMT_MAX_INFO_LENGTH 0x3FF // i.e., 12 bits with the top two zero #endif // _pidint_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/pidint_fns.h000066400000000000000000000201211261471605300171720ustar00rootroot00000000000000/* * Functions for working PID/integer lists. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _pidint_fns #define _pidint_fns #include "pidint_defns.h" // ============================================================================ // PIDINT LIST maintenance // ============================================================================ /* * Initialise a new pid/int list datastructure. */ extern int init_pidint_list(pidint_list_p list); /* * Build a new pid/int list datastructure. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_pidint_list(pidint_list_p *list); /* * Add a pid/integer pair to the end of the list * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int append_to_pidint_list(pidint_list_p list, uint32_t pid, int program); /* * Remove a pid/integer pair from the list * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remove_from_pidint_list(pidint_list_p list, uint32_t pid); /* * Tidy up and free a pid/int list datastructure after we've finished with it * * Clears the datastructure, frees it and returns `list` as NULL. * * Does nothing if `list` is already NULL. */ extern void free_pidint_list(pidint_list_p *list); /* * Report on a pid/int list's contents */ extern void report_pidint_list(pidint_list_p list, char *list_name, char *int_name, int pid_first); /* * Lookup a PID to find the corresponding integer value in a pid/int list. * * Returns 0 if the PID is in the list, -1 if it is not. */ extern int pid_int_in_pidint_list(pidint_list_p list, uint32_t pid, int *number); /* * Lookup a PID to find its index in a pid/int list. * * Note that if `list` is NULL, then -1 will be returned - this is to * allow the caller to make a query before they have read a list from the * bitstream. * * Returns its index (0 or more) if the PID is in the list, -1 if it is not. */ extern int pid_index_in_pidint_list(pidint_list_p list, uint32_t pid); /* * Lookup a PID to see if it is in a pid/int list. * * Note that if `list` is NULL, then FALSE will be returned - this is to * allow the caller to make a query before they have read a list from the * bitstream. * * Returns TRUE if the PID is in the list, FALSE if it is not. */ extern int pid_in_pidint_list(pidint_list_p list, uint32_t pid); /* * Check if two pid/int lists have the same content. * * Note that: * * - a list always compares as the same as itself * - two NULL lists compare as the same * - the *order* of PID/int pairs in the lists does not matter * * Returns TRUE if the two have the same content, FALSE otherwise. */ extern int same_pidint_list(pidint_list_p list1, pidint_list_p list2); /* * Report on a program stream list (a specialisation of report_pidint_list). * * - `list` is the stream list to report on * - `prefix` is NULL or a string to put before each line printed */ extern void report_stream_list(pidint_list_p list, char *prefix); // ============================================================================ // PMT data maintenance // ============================================================================ /* * Build a new PMT datastructure. * * `version_number` should be in the range 0-31, and will be treated as a * number modulo 32 if it is not. * * `PCR_pid` should be a legitimate PCR PID - i.e., in the range 0x0010 to * 0x1FFE, or 0x1FFF to indicate "unset". However, for convenience, the * value 0 will also be accepted, and converted to 0x1FFF. * * Returns (a pointer to) the new PMT datastructure, or NULL if some error * occurs. */ extern pmt_p build_pmt(uint16_t program_number, byte version_number, uint32_t PCR_pid); /* * Set the descriptor data on a PMT. Specifically, 'program info', * the descriptor data in the PMT "as a whole". * * Any previous program information for this PMT is lost. * * A copy of the program information bytes is taken. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int set_pmt_program_info(pmt_p pmt, uint16_t program_info_length, byte *program_info); /* * Add a program stream to a PMT datastructure * * If `ES_info_length` is greater than 0, then `ES_info` is copied. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int add_stream_to_pmt(pmt_p pmt, uint32_t elementary_PID, byte stream_type, uint16_t ES_info_length, byte *ES_info); /* * Remove a program stream from a PMT. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remove_stream_from_pmt(pmt_p pmt, uint32_t pid); /* * Tidy up and free a PMT datastructure after we've finished with it * * Clears the datastructure, frees it and returns `pmt` as NULL. * * Does nothing if `pmt` is already NULL. */ extern void free_pmt(pmt_p *pmt); /* * Lookup a PID to find its index in a PMT datastructure. * * Note that if `pmt` is NULL, then -1 will be returned. * * Returns its index (0 or more) if the PID is in the list, -1 if it is not. */ extern int pid_index_in_pmt(pmt_p pmt, uint32_t pid); /* * Lookup a PID to find the corresponding program stream information. * * Returns a pointer to the stream information if the PID is in the list, * NULL if it is not. */ extern pmt_stream_p pid_stream_in_pmt(pmt_p pmt, uint32_t pid); /* * Lookup a PID to see if it is in a PMT datastructure. * * Note that if `pmt` is NULL, then FALSE will be returned. * * Returns TRUE if the PID is in the PMT's stream list, FALSE if it is not. */ extern int pid_in_pmt(pmt_p pmt, uint32_t pid); /* * Check if two PMT datastructures have the same content. * * Note that: * * - a PMT datastructure always compares as the same as itself * - two NULL datastructures compare as the same * - the *order* of program streams in the PMTs does not matter * - descriptors must be identical as well, and byte order therein * does matter (this may need changing later on) * * Returns TRUE if the two have the same content, FALSE otherwise. */ extern int same_pmt(pmt_p pmt1, pmt_p pmt2); /* * Report on a PMT datastructure. * * - if `is_msg`, report as a message, otherwise as an error * - `prefix` is NULL or a string to put before each line printed * - `pmt` is the PMT to report on */ extern void report_pmt(int is_msg, char *prefix, pmt_p pmt); #endif // _pidint_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/printing.c000066400000000000000000000176501261471605300166770ustar00rootroot00000000000000/* * Support for printing out to stdout/stderr/elsewhere -- functions to use * instead of printf, etc. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include "compat.h" #include "printing_fns.h" #define DEBUG 0 // ============================================================ // Default printing functions // ============================================================ static void print_message_to_stdout(const char *message) { #if DEBUG fputs("1>>",stdout); #endif (void) fputs(message,stdout); } static void print_message_to_stderr(const char *message) { #if DEBUG fputs("2>>",stderr); #endif (void) fputs(message,stderr); } static void fprint_message_to_stdout(const char *format, va_list arg_ptr) { #if DEBUG fputs("3>>",stdout); #endif (void) vfprintf(stdout, format, arg_ptr); } static void fprint_message_to_stderr(const char *format, va_list arg_ptr) { #if DEBUG fputs("4>>",stderr); #endif (void) vfprintf(stderr, format, arg_ptr); } static void flush_stdout(void) { (void) fflush(stdout); } // ============================================================ // Print redirection defaults to all output going to stdout // ============================================================ struct print_fns { void (*print_message_fn) (const char *message); void (*print_error_fn) (const char *message); void (*fprint_message_fn) (const char *format, va_list arg_ptr); void (*fprint_error_fn) (const char *format, va_list arg_ptr); void (*flush_message_fn) (void); }; static struct print_fns fns = { print_message_to_stdout, print_message_to_stdout, fprint_message_to_stdout, fprint_message_to_stdout, flush_stdout }; #if DEBUG static void report_fns(const char *why) { printf("Printing bound to (%s) m:%p, e:%p, fm:%p, fe:%p\n",why, fns.print_message_fn, fns.print_error_fn, fns.fprint_message_fn, fns.fprint_error_fn); } #endif // ============================================================ // Functions for printing // ============================================================ /* * Prints the given string, as a normal message. */ extern void print_msg(const char *text) { #if DEBUG printf("m:%p %s",fns.print_message_fn,text); report_fns("m"); #endif fns.print_message_fn(text); } /* * Prints the given string, as an error message. */ extern void print_err(const char *text) { #if DEBUG printf("e:%p %s",fns.print_error_fn,text); report_fns("e"); #endif fns.print_error_fn(text); } /* * Prints the given format text, as a normal message. */ extern void fprint_msg(const char *format, ...) { va_list va_arg; va_start(va_arg, format); #if DEBUG printf("fm:%p %s",fns.fprint_message_fn,format); report_fns("fm"); #endif fns.fprint_message_fn(format, va_arg); va_end(va_arg); } /* * Prints the given formatted text, as an error message. */ extern void fprint_err(const char *format, ...) { va_list va_arg; va_start(va_arg, format); #if DEBUG printf("fe:%p %s",fns.fprint_error_fn,format); report_fns("fe"); #endif fns.fprint_error_fn(format, va_arg); va_end(va_arg); } /* * Prints the given formatted text, as a normal or error message. * If `is_msg`, then as a normal message, else as an error */ extern void fprint_msg_or_err(int is_msg, const char *format, ...) { va_list va_arg; va_start(va_arg, format); if (is_msg) { #if DEBUG printf("?m:%p %s",fns.fprint_message_fn,format); report_fns("?m"); #endif fns.fprint_message_fn(format, va_arg); } else { #if DEBUG printf("?e:%p %s",fns.fprint_error_fn,format); report_fns("?e"); #endif fns.fprint_error_fn(format, va_arg); } va_end(va_arg); } /* * Prints the given string, as a normal message. */ extern void flush_msg(void) { fns.flush_message_fn(); } // ============================================================ // Choosing what the printing functions do // ============================================================ /* * Calling this causes errors to go to stderr, and all other output * to go to stdout. This is the "traditional" mechanism used by * Unices. */ extern void redirect_output_stderr(void) { fns.print_message_fn = &print_message_to_stdout; fns.print_error_fn = &print_message_to_stderr; fns.fprint_message_fn = &fprint_message_to_stdout; fns.fprint_error_fn = &fprint_message_to_stderr; fns.flush_message_fn = &flush_stdout; #if DEBUG report_fns("traditional"); #endif } /* * Calling this causes all output to go to stdout. This is simpler, * and is likely to be more use to most users. * * This is the default state. */ extern void redirect_output_stdout(void) { fns.print_message_fn = &print_message_to_stdout; fns.print_error_fn = &print_message_to_stdout; fns.fprint_message_fn = &fprint_message_to_stdout; fns.fprint_error_fn = &fprint_message_to_stdout; fns.flush_message_fn = &flush_stdout; #if DEBUG report_fns("stdout"); #endif } /* * This allows the user to specify a set of functions to use for * formatted printing and non-formatted printing of errors and * other messages. * * It is up to the caller to ensure that all of the functions * make sense. All four functions must be specified. * * * `new_print_message_fn` takes a string and prints it out to the "normal" * output stream. * * `new_print_error_fn` takes a string and prints it out to the error output * stream. * * `new_fprint_message_fn` takes a printf-style format string and the * appropriate arguments, and writes the result out to the "normal" output. * * `new_fprint_error_fn` takes a printf-style format string and the * appropriate arguments, and writes the result out to the "error" output. * * `new_flush_msg_fn` flushes the "normal" message output. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int redirect_output( void (*new_print_message_fn) (const char *message), void (*new_print_error_fn) (const char *message), void (*new_fprint_message_fn) (const char *format, va_list arg_ptr), void (*new_fprint_error_fn) (const char *format, va_list arg_ptr), void (*new_flush_msg_fn) (void) ) { if (new_print_message_fn == NULL || new_print_error_fn == NULL || new_fprint_message_fn == NULL || new_fprint_error_fn == NULL || new_flush_msg_fn == NULL) return 1; fns.print_message_fn = new_print_message_fn; fns.print_error_fn = new_print_error_fn; fns.fprint_message_fn = new_fprint_message_fn; fns.fprint_error_fn = new_fprint_error_fn; fns.flush_message_fn = new_flush_msg_fn; #if DEBUG report_fns("specific"); #endif return 0; } extern void test_C_printing(void) { print_msg("C Message\n"); print_err("C Error\n"); fprint_msg("C Message %s\n","Fred"); fprint_err("C Error %s\n","Fred"); } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/printing_defns.h000066400000000000000000000023651261471605300200600ustar00rootroot00000000000000/* * Support for printing out to stdout/stderr/elsewhere -- functions to use * instead of printf, etc. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _printing_defns #define _printing_defns #include #include #endif // _printing_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/printing_fns.h000066400000000000000000000077001261471605300175450ustar00rootroot00000000000000/* * Support for printing out to stdout/stderr/elsewhere -- functions to use * instead of printf, etc. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _printing_fns #define _printing_fns #include "printing_defns.h" // ============================================================ // Functions for printing // ============================================================ /* * Prints the given string, as a normal message. */ extern void print_msg(const char *text); /* * Prints the given string, as an error message. */ extern void print_err(const char *text); /* * Prints the given format text, as a normal message. */ extern void fprint_msg(const char *format, ...); /* * Prints the given formatted text, as an error message. */ extern void fprint_err(const char *format, ...); /* * Prints the given formatted text, as a normal or error message. * If `is_msg`, then as a normal message, else as an error */ extern void fprint_msg_or_err(int is_msg, const char *format, ...); /* * Flush the message output */ extern void flush_msg(void); // ============================================================ // Choosing what the printing functions do // ============================================================ /* * Calling this causes errors to go to stderr, and all other output * to go to stdout. This is the "traditional" mechanism used by * Unices. */ extern void redirect_output_stderr(void); /* * Calling this causes all output to go to stdout. This is simpler, * and is likely to be more use to most users. * * This is the default state. */ extern void redirect_output_stdout(void); /* * This allows the user to specify a set of functions to use for * formatted printing and non-formatted printing of errors and * other messages. * * It is up to the caller to ensure that all of the functions * make sense. All four functions must be specified. * * * `new_print_message_fn` takes a string and prints it out to the "normal" * output stream. * * `new_print_error_fn` takes a string and prints it out to the error output * stream. * * `new_fprint_message_fn` takes a printf-style format string and the * appropriate arguments, and writes the result out to the "normal" output. * * `new_fprint_error_fn` takes a printf-style format string and the * appropriate arguments, and writes the result out to the "error" output. * * `new_flush_msg_fn` flushes the "normal" message output. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int redirect_output( void (*new_print_message_fn) (const char *message), void (*new_print_error_fn) (const char *message), void (*new_fprint_message_fn) (const char *format, va_list arg_ptr), void (*new_fprint_error_fn) (const char *format, va_list arg_ptr), void (*new_flush_msg_fn) (void) ); // Just for the moment extern void test_C_printing(void); #endif // _printing_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ps.c000066400000000000000000001754651261471605300155000ustar00rootroot00000000000000/* * Utilities for reading program stream data. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ps_fns.h" #include "ts_fns.h" #include "pes_fns.h" #include "pidint_fns.h" #include "misc_fns.h" #include "printing_fns.h" #define DEBUG 0 #define DEBUG_AC3 0 // How many bytes to look through for a pack header, before giving up #define PACK_HEADER_SEARCH_DISTANCE 100000 // If we lose where we are, should we look for the next PS pack header? // (instead of just giving up) #define RECOVER_BROKEN_PS 1 // ============================================================ // PS to TS datastructures // ============================================================ // Data we need to write PAT/PMT and otherwise manage our program streams // // DVD allows one video stream and up to 8 audio streams, // but we're only interested in a single video and a single audio stream struct program_data { // Information supplied by the user uint32_t transport_stream_id; uint32_t program_number; uint32_t pmt_pid; // PID to use for the PMT uint32_t pcr_pid; // PID to use for the PCR uint32_t video_pid; // PID to use for our single stream of video uint32_t audio_pid; // PID to use for our single stream of audio int video_stream; // Which stream id our video is (-1 means use first) int audio_stream; // Which stream id our audio is (-1 means use first) int want_ac3; // True means audio_stream is private_1/AC3 substream int audio_substream; // Which substream id if DVD and want_ac3 int video_type; // Is our video H.264, H.262, etc. (user must decide) int is_dvd; // Is our data DVD program stream (ditto) int output_dolby_as_dvb; // Output Dolby (AC-3) audio as DVB or ATSC? // Information derived from the data // PAT and PMT data pidint_list_p prog_list; pmt_p pmt; }; // ============================================================ // Read ahead support // ============================================================ /* * Read some more data into our read-ahead buffer. * * Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise * 1 if some error occurs. */ static inline int get_more_data(PS_reader_p ps) { // Call `read` directly - we don't particularly mind if we get a "short" // read, since we'll just catch up later on #ifdef _WIN32 int len = _read(ps->input,&ps->data,PS_READ_AHEAD_SIZE); #else ssize_t len = read(ps->input,&ps->data,PS_READ_AHEAD_SIZE); #endif if (len == 0) return EOF; else if (len == -1) { fprint_err("### Error reading next bytes: %s\n",strerror(errno)); return 1; } ps->data_posn += ps->data_len; // length of the *last* buffer ps->data_len = len; ps->data_end = ps->data + len; // one beyond the last byte ps->data_ptr = ps->data; // start at the beginning return 0; } /* * Build a program stream context attached to an input file. This handles * read-ahead buffering for the PS. * * - `input` is the file stream to read from. * - If `quiet`, then don't report on ignored bytes at the start of the file * - `ps` is the new PS context * * Returns 0 if all goes well, 1 otherwise. */ extern int build_PS_reader(int input, int quiet, PS_reader_p *ps) { int err; PS_reader_p new = malloc(SIZEOF_PS_READER); if (new == NULL) { print_err("### Unable to allocate program stream read context\n"); return 1; } new->input = input; new->data_posn = 0; new->data_len = 0; new->start = 0; err = get_more_data(new); if (err) { print_err("### Unable to start reading from new PS read context\n"); free(new); return 1; } // And look for the first pack header err = find_PS_pack_header_start(new,FALSE,PACK_HEADER_SEARCH_DISTANCE, &(new->start)); if (err) { fprint_err("### File does not appear to be PS\n" " Cannot find PS pack header in first %d bytes of file\n", PACK_HEADER_SEARCH_DISTANCE); free(new); return 1; } // Seeking won't work on standard input, so don't even try if (input != STDIN_FILENO) { // But we don't *really* want to have read its start yet err = seek_using_PS_reader(new,new->start); if (err) { print_err("### Error seeking to start of first pack header\n"); free(new); return 1; } } if (!quiet && new->start != 0) fprint_err("!!! PS file does not start with pack header\n" " First PS pack header is at " OFFSET_T_FORMAT "\n",new->start); *ps = new; return 0; } /* * Tidy up the PS read-ahead context after we've finished with it. * * Specifically: * * - free the datastructure * - set `ps` to NULL * * Does not close the associated file. */ extern void free_PS_reader(PS_reader_p *ps) { if (*ps != NULL) { (*ps)->input = -1; // "forget" our input free(*ps); *ps = NULL; } } /* * Open a PS file for reading. * * - `name` is the name of the file. If this is NULL, then standard input * is used. * - If `quiet`, then don't report on ignored bytes at the start of the file * - `ps` is the new PS context * * Returns 0 if all goes well, 1 otherwise. */ extern int open_PS_file(char *name, int quiet, PS_reader_p *ps) { int f; if (name == NULL) f = STDIN_FILENO; else { f = open_binary_file(name,FALSE); if (f == -1) return 1; } return build_PS_reader(f,quiet,ps); } /* * Close a PS file, and free the reader context * * (Doesn't close the file if it was standard input) * * Returns 0 if all goes well, 1 otherwise. */ extern int close_PS_file(PS_reader_p *ps) { if ((*ps)->input != STDIN_FILENO) { int err = close_file((*ps)->input); if (err) return 1; } free_PS_reader(ps); return 0; } /* * Given a program stream, attempt to determine if it holds H.262 or H.264 * data. * * Leaves the PS rewound to its "start". * * NOTE: It is probably better to use determine_PS_video_type(). * * - `ps` is the program stream to check (assumed just to have been * opened/built). This cannot be standard input, as it must be * seekable. * - `is_h264` is the result * * Returns 0 if all goes well, 1 if there was an error (including the * stream not appearing to be either). */ extern int determine_if_PS_is_h264(PS_reader_p ps, int *is_h264) { int err; PES_reader_p reader; // Try to decide what sort of data stream we have // The simplest (albeit clumsy) way to do this is to use technology // that already does this for us - i.e., build a temporary PES reader // around our file // It's then safe to build the temporary PES reader err = build_PS_PES_reader(ps,FALSE,FALSE,&reader); if (err) { print_err("### Error trying to determine PS stream type\n"); return 1; } // Which knows the file type *is_h264 = reader->is_h264; (void) free_PES_reader(&reader); // And then make sure that our file position is where we think it should be err = rewind_program_stream(ps); if (err) { print_err("### Error rewinding PS file after determining its type\n"); return 1; } return 0; } /* * Given a program stream, attempt to determine what type of video data it * contains. * * Leaves the PS rewound to its "start". * * - `ps` is the program stream to check (assumed just to have been * opened/built). This cannot be standard input, as it must be * seekable. * - `video_type` is the result. Calls determine_PES_video_type(). * * Returns 0 if all goes well, 1 if there was an error (including the * stream not appearing to be either). */ extern int determine_PS_video_type(PS_reader_p ps, int *video_type) { int err; PES_reader_p reader; // Try to decide what sort of data stream we have // The simplest (albeit clumsy) way to do this is to use technology // that already does this for us - i.e., build a temporary PES reader // around our file // It's then safe to build the temporary PES reader err = build_PS_PES_reader(ps,FALSE,FALSE,&reader); if (err) { print_err("### Error trying to determine PS stream type\n"); return 1; } // Which thinks it knows the file type *video_type = reader->video_type; (void) free_PES_reader(&reader); // And then make sure that our file position is where we think it should be err = rewind_program_stream(ps); if (err) { print_err("### Error rewinding PS file after determining its type\n"); return 1; } return 0; } /* * Seek within the PS file. * * Note that if the intent is to *rewind* to the start of the PS data, * then `rewind_program_stream` should be used instead, as offset 0 is * not necessarily the same as the start of the program stream. * * - `ps` is the PS read-ahead context * - `posn` is the file offset to seek to * * Return 0 if all goes well, 1 if something goes wrong */ extern int seek_using_PS_reader(PS_reader_p ps, offset_t posn) { int err = seek_file(ps->input,posn); if (err) return 1; ps->data_posn = posn; ps->data_len = 0; return get_more_data(ps); } /* * Rewind the PS context to the remembered "start of data" * * Returns 0 if all goes well, 1 if something goes wrong */ extern int rewind_program_stream(PS_reader_p ps) { return seek_using_PS_reader(ps,ps->start); } /* * Retrieve the next N bytes from the program stream, into an existing array. * * - `ps` is the PS read-ahead context * - `num_bytes` is how many bytes to read * - `buffer` is the buffer to read them into * - `posn` is the offset of said data in the file (NULL if the value is not * wanted). * * Returns 0 if all goes well, EOF if end-of-file is encountered before all * of the bytes have been read, 1 if some other error occurred. */ static int read_PS_bytes(PS_reader_p ps, int num_bytes, byte *buffer, offset_t *posn) { int err; int offset = 0; int num_bytes_wanted = num_bytes; int num_bytes_left = ps->data_end - ps->data_ptr; if (posn != NULL) *posn = ps->data_posn + (ps->data_ptr - ps->data); for (;;) { if (num_bytes_left < num_bytes_wanted) { memcpy(&(buffer[offset]),ps->data_ptr,num_bytes_left); offset += num_bytes_left; num_bytes_wanted -= num_bytes_left; err = get_more_data(ps); if (err) return err; num_bytes_left = ps->data_len; } else { memcpy(&(buffer[offset]),ps->data_ptr,num_bytes_wanted); ps->data_ptr += num_bytes_wanted; break; } } return 0; } // ============================================================ // Primitives // ============================================================ /* * Print out a stream id in a manner consistent with the PS usages * of the stream id values. */ extern void print_stream_id(int is_msg, byte stream_id) { byte number; char *str = NULL; switch (stream_id) { // H.222 Program stream specific codes case 0xB9: str = "PS MPEG_program_end_code"; break; case 0xBA: str = "PS Pack header start code"; break; case 0xBB: str = "PS System header start code"; break; case 0xBC: str = "PS Program stream map"; break; case 0xFF: str = "PS Program stream directory"; break; // Other "simple" values from H.222 Table 2-18, page 32 case 0xBD: str = "Private stream 1"; break; case 0xBE: str = "Padding stream"; break; case 0xBF: str = "Private stream 2"; break; case 0xF0: str = "ECM stream"; break; case 0xF1: str = "EMM stream"; break; case 0xF2: str = "DSMCC stream"; break; case 0xF3: str = "13522 stream"; break; case 0xF4: str = "H.222.1 A stream"; break; case 0xF5: str = "H.222.1 B stream"; break; case 0xF6: str = "H.222.1 C stream"; break; case 0xF7: str = "H.222.1 D stream"; break; case 0xF8: str = "H.222.1 E stream"; break; case 0xF9: str = "Ancillary stream"; break; case 0x00: str = "H.262 Picture"; break; case 0xB2: str = "H.262 User data"; break; case 0xB3: str = "H.262 Sequence header"; break; case 0xB4: str = "H.262 Sequence error"; break; case 0xB5: str = "H.262 Extension"; break; case 0xB7: str = "H.262 Sequence end"; break; case 0xB8: str = "H.262 Group start"; break; default: str = NULL; break; } if (str != NULL) fprint_msg_or_err(is_msg,str); else if (stream_id >= 0xC0 && stream_id <=0xDF) { number = stream_id & 0x1F; fprint_msg_or_err(is_msg,"Audio stream 0x%02X",number); } else if (stream_id >= 0xE0 && stream_id <= 0xEF) { number = stream_id & 0x0F; fprint_msg_or_err(is_msg,"Video stream 0x%X",number); } else if (stream_id >= 0xFC && stream_id <= 0xFE) fprint_msg_or_err(is_msg,"Reserved data stream"); else fprint_msg_or_err(is_msg,"Unrecognised stream id"); } /* * Look for the start (the first 4 bytes) of the next program stream packet. * * Assumes that (for some reason) alignment has been lost, and thus it is * necessary to scan forwards to find the next 00 00 01 prefix. * * Otherwise equivalent to a call of `read_PS_packet_start`. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then we want to explain what we're doing * - if `max` is non-zero, then it is the maximum number of bytes * to scan before giving up. * - `posn` is the file offset of the start of the packet * - `stream_id` is the identifying byte, after the 00 00 01 prefix. Note * that this is set correctly if MPEG_program_end_code was read, and is * 0 if an error occurred. * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, or * * 1 if some error (including the first 3 bytes not being 00 00 01) occurs. */ extern int find_PS_packet_start(PS_reader_p ps, int verbose, uint32_t max, offset_t *posn, byte *stream_id) { int err; byte prev1 = 0xff; byte prev2 = 0xff; byte prev3 = 0xff; uint32_t count = 0; *stream_id = 0; for (;;) { byte *ptr; for (ptr = ps->data_ptr; ptr < ps->data_end; ptr++) { if (prev3 == 0x00 && prev2 == 0x00 && prev1 == 0x01) { if (*ptr == 0xB9) // MPEG_program_end_code { if (verbose) print_msg("Stopping at MPEG_program_end_code\n"); *stream_id = 0xB9; return EOF; } else { *stream_id = *ptr; *posn = ps->data_posn + (ptr - ps->data) - 3; ps->data_ptr = ptr + 1; return 0; } } if (max > 0) { count ++; if (count > max) { fprint_err("### No PS packet start found in %d bytes\n",max); return 1; } } prev3 = prev2; prev2 = prev1; prev1 = *ptr; } // We've run out of data - get some more err = get_more_data(ps); if (err) return err; } } /* * Look for the next PS pack header. * * Equivalent to calling `find_PS_packet_start` until `stream_id` is 0xBA * (in other words, equivalent to having read the pack header start with * `read_PS_packet_start`). * * If you want to call `read_PS_packet_start` to read this pack header start * in again, then call ``seek_using_PS_reader(ps,posn)`` to reposition ready * to read it. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then the 00 00 01 XX sequences found will be logged * to stderr, indicating the progress of our search * - if `max` is non-zero, then it is the maximum number of bytes * to scan before giving up. * - `posn` is the file offset of the start of the packet found * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, or * * 1 if some error (including the first 3 bytes not being 00 00 01) occurs. */ extern int find_PS_pack_header_start(PS_reader_p ps, int verbose, uint32_t max, offset_t *posn) { int err; byte stream_id = 0; while (stream_id != 0xBA) { err = find_PS_packet_start(ps,verbose,max,posn,&stream_id); if (err) { print_err("### Error looking for PS pack header (0xBA)\n"); return 1; } if (verbose) { fprint_err(" Found: stream id %02X at " OFFSET_T_FORMAT " (", stream_id,*posn); print_stream_id(FALSE,stream_id); print_err(")\n"); } } return 0; } /* * Read in (the rest of) a PS packet according to its length. * * Suitable for use reading PS PES packets and PS system header packets. * * NOTE that the `data` buffer in the `packet` is realloc'ed by this * function. It is thus important to ensure that the `packet` datastructure * contains a NULL pointer for said buffer before the first call of this * function. * * - `ps` is the PS read-ahead context we're reading from * - `stream_id` identifies what sort of packet it is * - `packet` is the packet we're reading the PES packet into. * * Returns 0 if it succeeds, EOF if it unexpectedly reads end-of-file, and 1 * if some other error occurs. `packet->data` will be NULL if EOF is returned. */ extern int read_PS_packet_body(PS_reader_p ps, byte stream_id, PS_packet_p packet) { int err; byte buf[2]; // First, the packet length err = read_PS_bytes(ps,2,buf,NULL); if (err) { fprint_err("### %s reading PS packet length\n", (err==EOF?"Unexpected end of file":"Error")); if (packet->data!=NULL) free(packet->data); packet->data = NULL; return err; } packet->packet_length = (buf[0] << 8) | buf[1]; #if DEBUG fprint_msg("Packet length %d\n",packet->packet_length); #endif // Remember that the packet length is the length of data // *after* the packet length field. Also, it is only allowed // to be 0 within a Transport Stream, so it should never be 0 for us // - but let's check anyway if (packet->packet_length == 0) { print_err("### Packet has length 0 - not allowed in PS\n"); if (packet->data!=NULL) free(packet->data); packet->data = NULL; return 1; } // Since we are, in general, expecting to write the packet out again // at some point, it is convenient to also store the leading bytes #if 0 // XXX naughty stuff packet->data = realloc(packet->data,packet->packet_length + 6 + 10); #else packet->data = realloc(packet->data,packet->packet_length + 6); #endif if (packet->data == NULL) { print_err("### Unable to allocate PS packet data buffer\n"); return 1; } packet->data_len = packet->packet_length + 6; // So let us reestablish said leading bytes packet->data[0] = 0; packet->data[1] = 0; packet->data[2] = 1; packet->data[3] = stream_id; packet->data[4] = buf[0]; packet->data[5] = buf[1]; // And now we can read in the rest of the packet's data err = read_PS_bytes(ps,packet->packet_length,&(packet->data[6]),NULL); if (err) { fprint_err("### %s reading rest of PS packet\n", (err==EOF?"Unexpected end of file":"Error")); if (packet->data!=NULL) free(packet->data); packet->data = NULL; return err; } #if 0 // XXX naughty stuff - add some trailing zero bytes packet->data[packet->data_len + 0] = 0; packet->data[packet->data_len + 1] = 0; packet->data[packet->data_len + 2] = 0; packet->data[packet->data_len + 3] = 0; packet->data[packet->data_len + 4] = 0; packet->data[packet->data_len + 5] = 0; packet->data[packet->data_len + 6] = 0; packet->data[packet->data_len + 7] = 0; packet->data[packet->data_len + 8] = 0; packet->data[packet->data_len + 9] = 0; packet->packet_length += 10; packet->data_len += 10; #endif return 0; } /* * Read in the body of the pack header (but *not* the system header packets * therein). * * - `ps` is the PS read-ahead context we're reading from * - `hdr` is the packet we've read * * Returns 0 if it succeeds, EOF if it unexpectedly reads end-of-file, and * 1 if some other error occurs. */ extern int read_PS_pack_header_body(PS_reader_p ps, PS_pack_header_p hdr) { int err; byte dummy[8]; // a 3 bit length means no more than 7 stuffing bytes // Read just the first 8 bytes, in case it's an MPEG-1 pack header err = read_PS_bytes(ps,8,hdr->data,NULL); if (err) { fprint_err("### %s reading body of PS pack header\n", (err==EOF?"Unexpected end of file":"Error")); return err; } if ((hdr->data[0] & 0xF0) == 0x20) { #if DEBUG print_msg("ISO/IEC 11171-1/MPEG-1 pack header\n"); print_data(TRUE,"Pack header",hdr->data,8,8); #endif hdr->pack_stuffing_length = 0; // since it doesn't exist hdr->scr = (((uint64_t)(hdr->data[0] & 0x09)) << 29) | (((uint64_t) hdr->data[1] ) << 22) | (((uint64_t)(hdr->data[2] & 0xFE)) << 14) | (((uint64_t) hdr->data[3] ) << 7) | (((uint64_t)(hdr->data[4] & 0xFE)) >> 1); hdr->program_mux_rate = (((uint32_t)(hdr->data[5] & 0x7F)) << 15) | (((uint32_t) hdr->data[6] ) << 7) | (((uint32_t)(hdr->data[7] & 0xFE)) >> 1); // In MPEG-1, SCR = NINT(SysClockFreq * t[i]) % 2**33 // where SysClockFreq = 90,000 Hz // // In H.222.0, SCR = SCRbase[i] * 300 + SCRext[i] // = (((SysClockFreq * t[i]) DIV 300) % 2**33) * 300 + // ((SysClockFreq * t[i]) DIV 1) % 300 // where SysClockFreq = 27,000,000 Hz // Fudge these to match H.222.0 in case anyone tries to use them later on hdr->scr = hdr->scr * 300; hdr->scr_base = hdr->scr / 300; hdr->scr_extn = 0; // i.e., hdr->scr % 300 } else { #if DEBUG print_msg("ISO/IEC 13818-1/H.222.0 pack header\n"); #endif err = read_PS_bytes(ps,2,&(hdr->data[8]),NULL); if (err) { fprint_err("### %s reading last 2 bytes of body of PS pack header\n", (err==EOF?"Unexpected end of file":"Error")); return err; } #if DEBUG print_data(TRUE,"Pack header",hdr->data,10,10); #endif hdr->scr_base = (((uint64_t)(hdr->data[0] & 0x38)) << 27) | (((uint64_t)(hdr->data[0] & 0x03)) << 28) | (((uint64_t) hdr->data[1] ) << 20) | (((uint64_t)(hdr->data[2] & 0xF8)) << 12) | (((uint64_t)(hdr->data[2] & 0x03)) << 13) | (((uint64_t) hdr->data[3] ) << 5) | (((uint64_t)(hdr->data[4] & 0xF8)) >> 3); hdr->scr_extn = (((uint32_t)(hdr->data[4] & 0x03)) << 7) | (((uint32_t) hdr->data[5] ) >> 1); hdr->scr = hdr->scr_base * 300 + hdr->scr_extn; hdr->program_mux_rate = (((uint32_t)hdr->data[6] << 14)) | (((uint32_t)hdr->data[7] << 6)) | (((uint32_t)hdr->data[8] >> 2)); hdr->pack_stuffing_length = hdr->data[9] & 0x07; } #if DEBUG // XXX fprint_msg("Pack header body: scr_base " LLU_FORMAT ", scr_extn %u, scr " LLU_FORMAT ", mux rate %u, stuffing %d\n", hdr->scr_base,hdr->scr_extn,hdr->scr,hdr->program_mux_rate, hdr->pack_stuffing_length); #endif // And ignore that many stuffing bytes... if (hdr->pack_stuffing_length > 0) { err = read_PS_bytes(ps,hdr->pack_stuffing_length,dummy,NULL); if (err) { fprint_err("### %s reading PS pack header stuffing bytes\n", (err==EOF?"Unexpected end of file":"Error")); return err; } } return 0; } /* * Clear the contents of a PS packet datastructure. Frees the internal * `data` array. */ extern void clear_PS_packet(PS_packet_p packet) { if (packet->data != NULL) { free(packet->data); packet->data = NULL; packet->data_len = 0; } packet->packet_length = 0; } /* * Tidy up and free a PS packet datastructure after we've finished with it. * * Empties the PS packet datastructure, frees it, and sets `unit` to NULL. * * If `unit` is already NULL, does nothing. */ extern void free_PS_packet(PS_packet_p *packet) { if (*packet == NULL) return; clear_PS_packet(*packet); free(*packet); *packet = NULL; } /* * Read in the start (the first 4 bytes) of the next program stream packet. * * If the bytes read don't appear to be valid (i.e., they do not start with * the 00 00 01 prefix), then the next pack header will be sought and read in. * * Note that sequences of 00 bytes before the 00 00 01 will be ignored. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then we want to explain what we're doing * - `posn` is the file offset of the start of the packet * - `stream_id` is the identifying byte, after the 00 00 01 prefix. Note * that this is set correctly if MPEG_program_end_code was read, and is * 0 if an error occurred. * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, * * 2 if the bytes read are not 00 00 01 `stream_id`, or * * 1 if some other error occurs. */ extern int read_PS_packet_start(PS_reader_p ps, int verbose, offset_t *posn, byte *stream_id) { int err; byte buf[4]; *stream_id = 0; err = read_PS_bytes(ps,4,buf,posn); if (err == EOF) return EOF; else if (err) { print_err("### Error reading start of PS packet\n"); return 1; } // It's not uncommon to get a sequence of 00 bytes between packs // (in particular, after an audio pack). We don't really want to grumble // about such things, but just to cope if (buf[0] == 0 && buf[1] == 0 && buf[2] == 0) { #if 0 // XXX fprint_msg("// %02x %02x %02x %02x\n",buf[0],buf[1],buf[2],buf[3]); #endif while (buf[2] == 0) // we already know buf[0] and buf[1] are zero { buf[2] = buf[3]; err = read_PS_bytes(ps,1,&(buf[3]),posn); if (err == EOF) return EOF; else if (err) { print_err("### Error skipping 00 bytes before start of PS packet\n"); return 1; } } #if 0 // XXX fprint_msg("\\\\ %02x %02x %02x %02x\n",buf[0],buf[1],buf[2],buf[3]); #endif } if (buf[0] != 0 || buf[1] != 0 || buf[2] != 1) { fprint_err("!!! PS packet at " OFFSET_T_FORMAT " should start " "00 00 01, but instead found %02X %02X %02X\n", *posn,buf[0],buf[1],buf[2]); #if RECOVER_BROKEN_PS print_err("!!! Attempting to find next PS pack header\n"); err = find_PS_pack_header_start(ps,TRUE,0,posn); if (err == EOF) return EOF; else if (err) { print_err("### Error trying to find start of next pack header\n"); return 1; } fprint_err("!!! Continuing with PS pack header at " OFFSET_T_FORMAT "\n",*posn); *stream_id = 0xBA; return 0; #else return 2; #endif } *stream_id = buf[3]; #if DEBUG fprint_msg("Packet at " OFFSET_T_FORMAT ", stream id %02X (",*posn,*stream_id); print_stream_id(TRUE,*stream_id); print_msg(")\n"); #endif if (buf[3] == 0xB9) // MPEG_program_end_code { if (verbose) print_msg("Stopping at MPEG_program_end_code\n"); return EOF; } else return 0; } /* Determine the details of an AC3 stream, by looking at its start * * Naughtily assume that `data` is long enough... */ static inline void determine_ac3_details(byte *data, int verbose, byte *bsmod, byte *acmod) { // The end of the syncinfo int fscod = (data[4] & 0xC0) >> 6; int frmsizecode = (data[4] & 0x3F); // The start of the bit stream info int bsid = (data[5] & 0xF8) >> 3; *bsmod = (data[5] & 0x07); *acmod = (data[6] & 0xC0) >> 6; if (verbose) { fprint_msg(" fscod %x (sample rate %skHz)\n",fscod, (fscod==0?"48":fscod==1?"44.1":fscod==2?"32":"??")); fprint_msg(" frmsizecode %x\n",frmsizecode); fprint_msg(" bsid %x (%s)\n",bsid, (bsid==8?"standard":bsid==6?"A52a alternate": bsid<8?"standard subset":"???")); fprint_msg(" bsmod %x (%s)\n",*bsmod,BSMOD_STR(*bsmod,*acmod)); fprint_msg(" acmod %x (%s)\n",*acmod,ACMOD_STR(*acmod)); } } /* * Inspect the given PS packet, and determine if it contains AC3 or DTS audio data. * * - `packet` is the packet's data, already established as private_data_1 * - `is_dvd` is true if the data should be interpreted as DVD data * - if `verbose`, report on the details of what we find out * - `substream_index` returns the substream's index, taken from the low * nibble of the substream id, and adjusted to start at 0. This will be * a value in the range 0-7 for DTS, AC3 and LPCM, and in the range 0-1F * (0-31) for subpictures. * - for AC3, `bsmod` and `acmod` return the appropriate quantities, * otherwise they are 0. * * Returns one of the SUBSTREAM_* values. */ extern int identify_private1_data(struct PS_packet *packet, int is_dvd, int verbose, int *substream_index, byte *bsmod, byte *acmod) { // If this packet contains the start of a data packet, then // try to determine if it is AC-3 int PES_header_data_length = packet->data[6+2]; int what = SUBSTREAM_OTHER; byte *data; #if 0 // Hmm - the data never does seem to have the pusi bit set if (verbose) { int data_alignment_indicator = (packet->data[6] & 0x04); if (data_alignment_indicator) print_msg("*** Data aligned\n"); else print_msg("--- Data not aligned\n"); fprint_msg("*** PES header data length 0x%x (%d)\n", PES_header_data_length,PES_header_data_length); } #endif *substream_index = 0; *bsmod = 0; *acmod = 0; // This *should* be the start of the underlying packet // Note that PES_header_data_length should not be 0 for // non-video streams... data = packet->data + 6 + 3 + PES_header_data_length; // DVD data has some structure within private_stream_1 if (is_dvd) { int substream_id, frame_count, offset; substream_id = data[0]; frame_count = data[1]; offset = (data[2] << 8) | data[3]; if (0x20 <= substream_id && substream_id <= 0x3F) { what = SUBSTREAM_SUBPICTURES; *substream_index = substream_id - 0x20; } else if (0x80 <= substream_id && substream_id <= 0x87) { what = SUBSTREAM_AC3; *substream_index = substream_id - 0x80; } else if (0x88 <= substream_id && substream_id <= 0x8F) { what = SUBSTREAM_DTS; *substream_index = substream_id - 0x88; } else if (0xA0 <= substream_id && substream_id <= 0xA7) { what = SUBSTREAM_LPCM; *substream_index = substream_id - 0xA0; } if (verbose) { fprint_msg(">>> substream_id %02x (%s index %d)\n",substream_id, (what==SUBSTREAM_AC3?"AC3": what==SUBSTREAM_DTS?"DTS": what==SUBSTREAM_LPCM?"LPCM": what==SUBSTREAM_SUBPICTURES?"subpictures":"???"), *substream_index); fprint_msg(">>> frame_count %02x (%d)\n",frame_count,frame_count); fprint_msg(">>> offset %04x (%d)\n",offset,offset); } // For AC3 and DTS, it's easy to check that it *does* appear to be what it // says, so let's do so if (what == SUBSTREAM_AC3 || what == SUBSTREAM_DTS) { int packet_length = (packet->data[4] << 8) | packet->data[5]; byte *sub_data = data + 3 + offset; // Roughly check if the offset looks plausible // TODO: make this check more robust! if (sub_data >= packet->data + packet_length) // leave off the + 6 { // Looks like it's a silly offset, so probably NOT what we want if (verbose) fprint_msg("*** expected %s, but data at %p is beyond" " packet->end at %p\n",(what==SUBSTREAM_DTS?"DTS":"AC3"), sub_data,packet->data+6+packet_length); what = SUBSTREAM_ERROR; // we definitely mustn't try to interpret it! } else if (what == SUBSTREAM_AC3 && !(sub_data[0] == 0x0B && sub_data[1] == 0x77)) { fprint_msg("*** expected AC3 sync 0x0B77, but found 0x%02x%02x\n", sub_data[0],sub_data[1]); what = SUBSTREAM_ERROR; } else if (what == SUBSTREAM_DTS && !(sub_data[0] == 0x7F && sub_data[1] == 0xFE && sub_data[2] == 0x80 && sub_data[3] == 0x01)) { fprint_msg("*** expected DTS sync 0x7FFE8001," " but found 0x%02x%02x%02x%02x\n", sub_data[0],sub_data[1],sub_data[2],sub_data[3]); what = SUBSTREAM_ERROR; } if (what == SUBSTREAM_AC3) determine_ac3_details(sub_data,verbose,bsmod,acmod); } } else { // For non-DVD data, we have to decide for ourselves what we've got if (data[0] == 0x0B && data[1] == 0x77) { what = SUBSTREAM_AC3; determine_ac3_details(data,verbose,bsmod,acmod); } else if (data[0] == 0x7F && data[1] == 0xFE && data[2] == 0x80 && data[3] == 0x01) what = SUBSTREAM_DTS; } if (verbose) { switch (what) { case SUBSTREAM_AC3: print_msg("*** Looks like AC3\n"); break; case SUBSTREAM_DTS: print_msg("*** Looks like DTS\n"); break; case SUBSTREAM_LPCM: print_msg("*** Looks like LPCM\n"); break; case SUBSTREAM_SUBPICTURES: print_msg("*** Looks like sub-pictures\n"); break; case SUBSTREAM_OTHER: fprint_msg("*** Other substream: %02x %02x %02x %02x\n", data[0],data[1],data[2],data[3]); break; default: fprint_msg("*** Error recognising substream: %02x %02x %02x %02x\n", data[0],data[1],data[2],data[3]); break; } } return what; } // ============================================================ // PS to TS functions // ============================================================ /* * Write out a video packet * * - `output` is the transport stream we're writing to * - `header` is the data from the PS pack header, including its SCR data * - `stream_id` is the stream id of the PS packet we're writing * - `packet` is the PS packet itself * - `prog_data` is the programming information we're using * - `num_video_ignored` will be be updated if we ignore this video packet * - `num_video_written` will be updated if we don't * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ static int write_video(TS_writer_p output, struct PS_pack_header *header, byte stream_id, struct PS_packet *packet, struct program_data *prog_data, int *num_video_ignored, int *num_video_written, int verbose, int quiet) { int err; // Unless the user has requested a particular video stream, we want // to use the first we find... // (For DVD this should also be the only stream, since DVD only allows // one video stream, but that's not for us to check.) if (prog_data->video_stream == -1) { prog_data->video_stream = stream_id; } else if (stream_id != prog_data->video_stream) { static int ignored_stream[NUMBER_VIDEO_STREAMS] = {0}; int this_stream = stream_id & 0x0F; if (!ignored_stream[this_stream]) { ignored_stream[this_stream] = TRUE; if (!quiet) fprint_msg("Ignoring video stream 0x%x (%d)\n",this_stream,this_stream); } (*num_video_ignored) ++; return 0; } if (*num_video_written == 0) { if (!quiet) fprint_msg("Video: stream %d, PID 0x%03x, stream type 0x%02x\n" " %s\n", stream_id & 0x0F,prog_data->video_pid,prog_data->video_type, h222_stream_type_str(prog_data->video_type)); err = add_stream_to_pmt(prog_data->pmt,prog_data->video_pid, prog_data->video_type,0,NULL); if (err) return 1; prog_data->pmt->version_number ++; // And it makes sense to write out our (updated) program // information before we write out the first packet of // the new video (!) err = write_pat_and_pmt(output, prog_data->transport_stream_id, prog_data->prog_list, prog_data->pmt_pid, prog_data->pmt); if (err) { print_err("### Error writing TS program data before video packet\n"); return 1; } } // This is our video stream - output it as such err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len, prog_data->video_pid, DEFAULT_VIDEO_STREAM_ID, TRUE,header->scr_base,header->scr_extn); if (err) return 1; (*num_video_written) ++; if (verbose) { print_msg("v"); flush_msg(); } return 0; } /* * Write out the data for our DVD private_stream_1 audio packet * * - `output` is the transport stream we're writing to * - `packet` is the PS packet itself * - `prog_data` is the programming information we're using * * Returns 0 if all went well, 1 if something went wrong. */ static int write_DVD_AC3_data(TS_writer_p output, struct PS_packet *packet, struct program_data *prog_data) { // DVD private_stream_1 is packaged as substreams - we need to unpack // it before we output it // Basically, looking at the substream offset: // 0 means there is no first frame in this packet (i.e., the data // is the middle/end of a frame // 1 means the data *starts* with the first frame (to which any // PTS applies) // N means that the frame to which the PTS applies starts at // offset N-1 int PES_header_data_length = packet->data[6+2]; byte *data = packet->data + 6 + 3 + PES_header_data_length; int data_len = packet->data_len - 6 - 3 - PES_header_data_length; //int substream_id = data[0]; int frame_count = data[1]; // frames starting in this packet int offset = (data[2] << 8) | data[3]; // If there is no PTS in this packet, do we need to split, even // if the offset is > 1? I'll assume not... int got_pts; uint64_t pts; int PES_packet_length = (packet->data[4] << 8) | packet->data[5]; int err = find_PTS_in_PES(packet->data,packet->data_len,&got_pts,&pts); if (err) { print_err("### Error looking for PTS in PES packet\n"); return 1; } #if DEBUG_AC3 fprint_msg(".. frame_count=%d, offset=%4d, got_pts=%d ", frame_count,offset,got_pts); if (frame_count > 0 && offset > 0) { if (data[offset+3]==0x0B && data[offset+4]==0x77) print_msg("(frame is AC3)\n"); else fprint_msg("(frame appears to start %02x %02x)\n", data[offset+3],data[offset+4]); } #endif if (frame_count == 0 || offset <= 1 || !got_pts) { // We can output the data from this packet "unsplit". // However, first we need to create a new packet that does not // contain the DVD substream header - this is most easily done // by (a) copying the audio data bytes "down" over the header // we want to lose, and (b) adjusting the various packet length // fields appropriately #if DEBUG_AC3 fprint_msg("move data down 4, leaving %4d\n",data_len-4); #endif (void) memmove(data,data+4,data_len-4); // 4 bytes of substream header // After copying, need to remember to adjust the packet length PES_packet_length -= 4; // still 4 bytes of substream header packet->data[4] = (PES_packet_length & 0xFF00) >> 8; packet->data[5] = (PES_packet_length & 0x00FF); // And we then have something suitable for outputting as-is err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len-4, prog_data->audio_pid, PRIVATE1_AUDIO_STREAM_ID, FALSE,0,0); if (err) return 1; } else { // We need to output the data *before* the "first" packet // in a plain PES packet, by itself, and then output the // packet to which the PTS applies as a separate PES packet. #if DEBUG_AC3 fprint_msg("write first %4d bytes, then move data down %4d, leaving %4d\n", offset-1,3+offset,data_len-3-offset); #endif // First, the part before the first packet... err = write_ES_as_TS_PES_packet(output,data+4,offset-1, prog_data->audio_pid, PRIVATE1_AUDIO_STREAM_ID); if (err) return 1; // And we can then do the "move the data down" trick // Remember that offset 1 means the first byte after the substream // header, which we would normally expect to be offset 0... (void) memmove(data,data+3+offset,data_len-3-offset); // After copying, need to remember to adjust the packet length PES_packet_length -= 3 + offset; packet->data[4] = (PES_packet_length & 0xFF00) >> 8; packet->data[5] = (PES_packet_length & 0x00FF); // And we then have something suitable for outputting as-is err = write_PES_as_TS_PES_packet(output,packet->data, packet->data_len-3-offset, prog_data->audio_pid, PRIVATE1_AUDIO_STREAM_ID, FALSE,0,0); if (err) return 1; } return 0; } /* * Write out an audio packet * * - `output` is the transport stream we're writing to * - `stream_id` is the stream id of the PS packet we're writing * - `packet` is the PS packet itself * - `prog_data` is the programming information we're using * - `num_audio_ignored` will be be updated if we ignore this audio packet * - `num_audio_written` will be updated if we don't * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ static int write_audio(TS_writer_p output, byte stream_id, struct PS_packet *packet, struct program_data *prog_data, int *num_audio_ignored, int *num_audio_written, int verbose, int quiet) { int err; int substream_index = -1; byte bsmod = 0xFF; byte asmod = 0xFF; int is_h222_pes = IS_H222_PES(packet->data); // For MPEG audio, unless the user has requested a particular audio stream, // we want to use the first we find. // For AC3, we only want audio from the requested substream // (DVD allows up to 8 audio streams, but we're only outputting one. // // Note that we assume that any audio data using private stream 1 to // transmit DVD-style audio packets will also be using PES (H.222.0) // packeting - i.e., *not* using MPEG-1 packets. This allows the code // to "dissect" DVD audio packets to be simpler, and seems a reasonable // assumption. if (prog_data->audio_stream == -1) // take the first audio stream we find... { if (stream_id == PRIVATE1_AUDIO_STREAM_ID && is_h222_pes) { // Find out what type of data this is int what = identify_private1_data(packet,prog_data->is_dvd,verbose, &substream_index,&bsmod,&asmod); if (what != SUBSTREAM_AC3) { // We're not interested in it return 0; } prog_data->audio_stream = stream_id; prog_data->audio_substream = substream_index; // only meaningful for DVD } else // some other ("normal") audio stream prog_data->audio_stream = stream_id; } else if (stream_id != prog_data->audio_stream) { if (!quiet) { static int ignored_stream[NUMBER_AUDIO_STREAMS] = {0}; int this_stream = stream_id & 0x1F; if (!ignored_stream[this_stream]) { ignored_stream[this_stream] = TRUE; fprint_msg("Ignoring audio stream 0x%x (%d)\n",this_stream,this_stream); } } (*num_audio_ignored) ++; return 0; } else if (prog_data->is_dvd && stream_id == PRIVATE1_AUDIO_STREAM_ID && prog_data->audio_stream == PRIVATE1_AUDIO_STREAM_ID && is_h222_pes) { // Check if this is the right substream int what = identify_private1_data(packet,prog_data->is_dvd,verbose, &substream_index,&bsmod,&asmod); if (what != SUBSTREAM_AC3) { if (!quiet) { #define MAX_IGNORED_NON_AC3 10 // report the first 10 substreams we're ignoring static uint32_t ignored_non_AC3[MAX_IGNORED_NON_AC3] = {0}; uint32_t lookfor = (what << 16) | substream_index; int ii; for (ii = 0; ii < MAX_IGNORED_NON_AC3; ii++) { if (ignored_non_AC3[ii] == lookfor) break; // we've reported it before else if (ignored_non_AC3[ii] == 0) { // We've not reported it before, and have room to remember it ignored_non_AC3[ii] = lookfor; fprint_msg("Ignoring %sprivate_stream_1 substream 0x%x (%d)" " containing %s\n", (SUBSTREAM_IS_AUDIO(what)?"":"non-audio "), substream_index,substream_index,SUBSTREAM_STR(what)); break; } } } if (SUBSTREAM_IS_AUDIO(what)) (*num_audio_ignored) ++; return 0; } else if (substream_index != prog_data->audio_substream) { if (!quiet) { static int ignored_ac3_substream[NUMBER_AC3_SUBSTREAMS] = {0}; if (!ignored_ac3_substream[substream_index]) { ignored_ac3_substream[substream_index] = TRUE; fprint_msg("Ignoring private_stream_1 substream 0x%x (%d) " "containing AC3\n",substream_index,substream_index); } } (*num_audio_ignored) ++; return 0; } } if (*num_audio_written == 0) { byte audio_stream_type; if (stream_id == PRIVATE1_AUDIO_STREAM_ID) { if (prog_data->output_dolby_as_dvb) audio_stream_type = DVB_DOLBY_AUDIO_STREAM_TYPE; else audio_stream_type = ATSC_DOLBY_AUDIO_STREAM_TYPE; } else audio_stream_type = MPEG2_AUDIO_STREAM_TYPE; if (!quiet) { if (stream_id == PRIVATE1_AUDIO_STREAM_ID) { print_msg("Audio: private stream 1,"); if (prog_data->is_dvd && is_h222_pes) fprint_msg(" substream %d,",prog_data->audio_substream); fprint_msg(" PID 0x%03x, AC-3 (Dolby)\n",prog_data->audio_pid); fprint_msg(" %s\n audio coding mode %s\n", BSMOD_STR(bsmod,asmod),ACMOD_STR(asmod)); } else { fprint_msg("Audio: stream %d, PID 0x%03x, stream type 0x%02x = %s\n", stream_id & 0x1F,prog_data->audio_pid,audio_stream_type, h222_stream_type_str(audio_stream_type)); } } if (audio_stream_type == DVB_DOLBY_AUDIO_STREAM_TYPE) { byte desc[] = {0x6A, 0x01, 0x00}; int desc_len = 3; err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid, audio_stream_type,desc_len,desc); } else if (audio_stream_type == ATSC_DOLBY_AUDIO_STREAM_TYPE) { byte desc[] = {0x05, 0x04, 0x41, 0x43, 0x2D, 0x33}; int desc_len = 6; err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid, audio_stream_type,desc_len,desc); } else err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid, audio_stream_type,0,NULL); if (err) return 1; prog_data->pmt->version_number ++; // And it makes sense to write out our (updated) program // information before we write out the first packet of // the new audio err = write_pat_and_pmt(output, prog_data->transport_stream_id, prog_data->prog_list, prog_data->pmt_pid, prog_data->pmt); if (err) { print_err("### Error writing TS program data before audio packet\n"); return 1; } } if (prog_data->is_dvd && stream_id == PRIVATE1_AUDIO_STREAM_ID && is_h222_pes) { // Unpack the DVD substreams before outputting them err = write_DVD_AC3_data(output,packet,prog_data); if (err) return 1; } else { err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len, prog_data->audio_pid, (prog_data->want_ac3? PRIVATE1_AUDIO_STREAM_ID: DEFAULT_AUDIO_STREAM_ID), FALSE,0,0); if (err) return 1; } (*num_audio_written) ++; if (verbose) { print_msg("a"); flush_msg(); } return 0; } /* * Read program stream and write transport stream * * - `ps` is the program stream * - `output` is the transport stream * - `pad_start` is the number of filler TS packets to start the output * with. * - `program_repeat` is how often (after how many PS packs) to repeat * the program information (PAT/PMT) * - `is_dvd` should be true if this input represents DVD data; i.e., with * private_stream_1 used for AC-3/DTS/etc., and with substream headers * therein. * - `video_stream` indicates which video stream we want - i.e., the stream * with id 0xE0 + . -1 means the first encountered. * - `audio_stream` indicates which audio stream we want. If `want_ac3_audio` * is false, then this will be the stream with id 0xC0 + , * or -1 for the first audio stream encountered. * - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want * audio from private_stream_1 (0xBD) with substream id , * otherwise we ignore `audio_stream` and assume that all data in * private_stream_1 is the audio we want. * - `output_dolby_as_dvb` should be true if Dolby (AC-3) audio (if selected) should * be output using the DVB stream type 0x06, false if using the ATSC stream * type 0x81. This is ignored if the audio being output is not Dolby. * - `pmt_pid` is the PID of the PMT to write * - `pcr_pid` is the PID of the TS unit containing the PCR * - `video_pid` is the PID for the video we write * - `keep_audio` is true if the audio stream should be output, false if * it should be ignored * - `audio_pid` is the PID for the audio we write * - if `max` is non-zero, then we want to stop reading after we've read * `max` packs * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ static int _ps_to_ts(PS_reader_p ps, TS_writer_p output, struct program_data *prog_data, int pad_start, int program_repeat, int keep_audio, int max, int verbose, int quiet) { int ii, err; offset_t posn = 0; // The location in the input file of the current packet int count = 0; // Number of PS packets byte stream_id; // The packet's stream id int end_of_file = FALSE; // Summary data int num_packs = 0; int num_audio_written = 0; int num_video_written = 0; int num_video_ignored = 0; int num_audio_ignored = 0; struct PS_packet packet = {0}; struct PS_pack_header header = {0}; // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; iipmt_pid,prog_data->pcr_pid); // Read the start of the first packet (we confidently expect this // to be a pack header) err = read_PS_packet_start(ps,verbose,&posn,&stream_id); if (err == EOF) { print_err("### Error reading first pack header\n"); print_err(" Unexpected end of PS at start of stream\n"); return 1; } else if (err) { print_err("### Error reading first pack header\n"); return 1; } count ++; if (stream_id != 0xba) { print_err("### Program stream does not start with pack header\n"); fprint_err(" First packet has stream id %02X (",stream_id); print_stream_id(FALSE,stream_id); print_err(")\n"); return 1; } // But given that, we can now happily loop reading in packs // I *think* using this macro makes the code marginally more readable, // and it helps emphasise that the code *is* identical each time #define READ_NEXT_PS_PACKET_START \ err = read_PS_packet_start(ps,FALSE,&posn,&stream_id); \ if (err == EOF) \ { \ end_of_file = TRUE; \ break; \ } \ else if (err) \ return 1; \ count ++; for (;;) { int num_system_headers = 0; if (max > 0 && num_packs >= max) { if (verbose) fprint_msg("Stopping after %d packs\n",num_packs); return 0; } num_packs ++; // Write out our program data every so often, to give the reader // a chance to resynchronise with our program stream if (num_packs % program_repeat == 0) { if (verbose) { print_msg("PGM"); flush_msg(); } err = write_pat_and_pmt(output, prog_data->transport_stream_id, prog_data->prog_list, prog_data->pmt_pid, prog_data->pmt); if (err) { print_err("### Error writing out TS program data\n"); return 1; } } err = read_PS_pack_header_body(ps,&header); if (err) { fprint_err("### Error reading data for pack header starting at " OFFSET_T_FORMAT "\n",posn); return 1; } // Look at the start of the next packet READ_NEXT_PS_PACKET_START; // If it's a system header, ignore it if (stream_id == 0xbb) { err = read_PS_packet_body(ps,stream_id,&packet); if (err) { fprint_err("### Error reading system header starting at " OFFSET_T_FORMAT "\n",posn); return 1; } num_system_headers ++; READ_NEXT_PS_PACKET_START; } if (end_of_file) break; // Then read the data packets while (stream_id != 0xba) // i.e., until the start of the next pack { err = read_PS_packet_body(ps,stream_id,&packet); if (err) { fprint_err("### Error reading PS packet starting at " OFFSET_T_FORMAT "\n",posn); return 1; } if (IS_AUDIO_STREAM_ID(stream_id)) { if (keep_audio) { err = write_audio(output,stream_id,&packet,prog_data, &num_audio_ignored,&num_audio_written, verbose,quiet); if (err) { fprint_err("### Error writing audio packet at " OFFSET_T_FORMAT " to TS\n",posn); return 1; } } } else if (IS_VIDEO_STREAM_ID(stream_id)) { err = write_video(output,&header,stream_id,&packet,prog_data, &num_video_ignored,&num_video_written,verbose,quiet); if (err) { fprint_err("### Error writing video packet at " OFFSET_T_FORMAT " to TS\n",posn); return 1; } } else if (verbose) { // For the moment, we ignore program stream map (0xBC) and // program stream directory (0xFF), and indeed everything else } READ_NEXT_PS_PACKET_START; } if (end_of_file) break; } clear_PS_packet(&packet); if (verbose) print_msg("\n"); if (!quiet) { fprint_msg("Packets (total): %6d\n",count); fprint_msg("Packs: %6d\n",num_packs); fprint_msg("Video packets written: %6d\n",num_video_written); fprint_msg("Audio packets written: %6d\n",num_audio_written); if (num_video_ignored > 0) fprint_msg("Video packets ignored: %6d\n",num_video_ignored); if (num_audio_ignored > 0) fprint_msg("Audio packets ignored: %6d\n",num_audio_ignored); } return 0; } /* * Read program stream and write transport stream * * - `ps` is the program stream * - `output` is the transport stream * - `pad_start` is the number of filler TS packets to start the output * with. * - `program_repeat` is how often (after how many PS packs) to repeat * the program information (PAT/PMT) * - `video_type` indicates what type of video is being transferred. It should * be VIDEO_H264, VIDEO_H262, etc. * - `is_dvd` should be true if this input represents DVD data; i.e., with * private_stream_1 used for AC-3/DTS/etc., and with substream headers * therein. * - `video_stream` indicates which video stream we want - i.e., the stream * with id 0xE0 + . -1 means the first encountered. * - `audio_stream` indicates which audio stream we want. If `want_ac3_audio` * is false, then this will be the stream with id 0xC0 + , * or -1 for the first audio stream encountered. * - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want * audio from private_stream_1 (0xBD) with substream id , * otherwise we ignore `audio_stream` and assume that all data in * private_stream_1 is the audio we want. * - `output_dolby_as_dvb` should be true if Dolby (AC-3) audio (if selected) should * be output using the DVB stream type 0x06, false if using the ATSC stream * type 0x81. This is ignored if the audio being output is not Dolby. * - `pmt_pid` is the PID of the PMT to write * - `pcr_pid` is the PID of the TS unit containing the PCR * - `video_pid` is the PID for the video we write * - `keep_audio` is true if the audio stream should be output, false if * it should be ignored * - `audio_pid` is the PID for the audio we write * - if `max` is non-zero, then we want to stop reading after we've read * `max` packs * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ extern int ps_to_ts(PS_reader_p ps, TS_writer_p output, int pad_start, int program_repeat, int video_type, int is_dvd, int video_stream, int audio_stream, int want_ac3_audio, int output_dolby_as_dvb, uint32_t pmt_pid, uint32_t pcr_pid, uint32_t video_pid, int keep_audio, uint32_t audio_pid, int max, int verbose, int quiet) { int err; struct program_data prog_data = {0}; prog_data.transport_stream_id = 1; prog_data.program_number = 1; prog_data.pmt_pid = pmt_pid; prog_data.pcr_pid = pcr_pid; prog_data.video_pid = video_pid; prog_data.audio_pid = audio_pid; prog_data.video_type = video_type; prog_data.output_dolby_as_dvb = output_dolby_as_dvb; prog_data.video_stream = video_stream; prog_data.want_ac3 = want_ac3_audio; prog_data.is_dvd = is_dvd; if (want_ac3_audio) { prog_data.audio_stream = PRIVATE1_AUDIO_STREAM_ID; if (is_dvd) prog_data.audio_substream = audio_stream; else prog_data.audio_substream = -1; // use the first we find } else prog_data.audio_stream = audio_stream; // We have one program - we'll make it program 1 #define PROGRAM_NUMBER 1 err = build_pidint_list(&prog_data.prog_list); if (err) return 1; err = append_to_pidint_list(prog_data.prog_list,pmt_pid,PROGRAM_NUMBER); if (err) { free_pidint_list(&prog_data.prog_list); return 1; } prog_data.pmt = build_pmt(PROGRAM_NUMBER,0,pcr_pid); if (err) { free_pidint_list(&prog_data.prog_list); return 1; } err = _ps_to_ts(ps,output,&prog_data,pad_start,program_repeat,keep_audio, max,verbose,quiet); if (err) { free_pidint_list(&prog_data.prog_list); free_pmt(&prog_data.pmt); return 1; } free_pidint_list(&prog_data.prog_list); free_pmt(&prog_data.pmt); return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ps2ts.c000066400000000000000000000414371261471605300161200ustar00rootroot00000000000000/* * Convert a Program Stream to Transport Stream. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "pes_fns.h" #include "ps_fns.h" #include "ts_fns.h" #include "tswrite_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" static void print_usage() { print_msg( "Usage: ps2ts [switches] [] []\n" "\n" ); REPORT_VERSION("ps2ts"); print_msg( "\n" " Convert an H.222 program stream to H.222 transport stream.\n" "\n" " This program does not make use of any Program Stream Map packets\n" " in the data (mainly because I have yet to see data with any). This\n" " means that the program has to determine the stream type of the data\n" " based on the first few ES units.\n" "\n" " This program does not output more than one video and one audio\n" " stream. If the program stream data contains more than one of each,\n" " the first will be used, and the others ignored (with a message\n" " indicating this).\n" "\n" " It is assumed that the video stream will contain DTS values in its\n" " PES packets at reasonable intervals, which can be used as PCR values\n" " in the transport stream, and thus the video stream's PID can be used\n" " as the PCR PID in the transport stream.\n" "\n" "Files:\n" " is a file containing the program stream data\n" " (but see -stdin below)\n" " is a transport stream file\n" " (but see -stdout and -host below)\n" "\n" "Input switches:\n" " -stdin Take input from , instead of a named file\n" " -dvd The PS data is from a DVD. This is the default.\n" " This switch has no effect on MPEG-1 PS data.\n" " -notdvd, -nodvd The PS data is not from a DVD.\n" " The DVD specification stores AC-3 (Dolby), DTS and\n" " other audio in a specialised manner in private_stream_1.\n" " -vstream Take video from video stream (0..7).\n" " The default is the first video stream found.\n" " -astream Take audio from audio stream (0..31).\n" " The default is the first audio stream found\n" " (this includes private_stream_1 on non-DVD streams).\n" " -ac3stream Take audio from AC3 substream (0..7), from\n" " private_stream_1. This implies -dvd.\n" " (If audio is being taken from a substream, the user\n" " is assumed to have determined which one is wanted,\n" " e.g., using psreport)\n" "\n" "Output switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdout Write output to , instead of a named file\n" " Forces -quiet and -err stderr.\n" " -host , -host :\n" " Writes output (over TCP/IP) to the named ,\n" " instead of to a named file. If is not\n" " specified, it defaults to 88.\n" " -vpid is the video PID to use for the data.\n" " Use '-vpid 0x' to specify a hex value.\n" " Defaults to 0x68.\n" " -apid is the audio PID to use for the data.\n" " Use '-apid 0x' to specify a hex value.\n" " Defaults to 0x67.\n" " -noaudio Don't output the audio data\n" " -pmt is the PMT PID to use.\n" " Use '-pmt 0x' to specify a hex value.\n" " Defaults to 0x66\n" " -prepeat Output the program data (PAT/PMT) after every \n" " PS packs. Defaults to 100.\n" " -pad Pad the start with filler TS packets, to allow\n" " a TS reader to synchronize with the datastream.\n" " Defaults to 8.\n" "\n" "General switches:\n" " -verbose, -v Print a 'v' for each video packet and an 'a' for \n" " each audio packet, as it is read\n" " -quiet, -q Only output error messages\n" " -max , -m Maximum number of PS packs to read\n" "\n" "Stream type:\n" " When the TS data is being output, it is flagged to indicate whether\n" " it conforms to H.262, H.264, etc. It is important to get this right, as\n" " it will affect interpretation of the TS data.\n" "\n" " If input is from a file, then the program will look at the start of\n" " the file to determine if the stream is H.264 or H.262 data. This\n" " process may occasionally come to the wrong conclusion, in which case\n" " the user can override the choice using the following switches.\n" "\n" " If input is from standard input (via -stdin), then it is not possible\n" " for the program to make its own decision on the input stream type.\n" " Instead, it defaults to H.262, and relies on the user indicating if\n" " this is wrong.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" " -mp42 Force the program to treat the input as MPEG-4/Part 2.\n" " -vtype Force the program to treat the input as video of\n" " stream type (e.g., 0x42 means AVS video). It is\n" " up to the user to specify a valid .\n" "\n" " If the audio stream being output is Dolby (AC-3), then the stream type\n" " used to output it differs for DVB (European) and ATSC (USA) data. It\n" " may be specified as follows:\n" "\n" " -dolby dvb Use stream type 0x06 (the default)\n" " -dolby atsc Use stream type 0x81\n" ); } int main(int argc, char **argv) { int use_stdin = FALSE; int use_stdout = FALSE; int use_tcpip = FALSE; int port = 88; // Useful default port number char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; PS_reader_p ps = NULL; TS_writer_p output = NULL; int verbose = FALSE; int quiet = FALSE; int max = 0; uint32_t pmt_pid = 0x66; uint32_t video_pid = 0x68; uint32_t pcr_pid = video_pid; // Use PCRs from the video stream uint32_t audio_pid = 0x67; int keep_audio = TRUE; int repeat_program_every = 100; int pad_start = 8; int err = 0; int ii = 1; int video_type = VIDEO_H262; // hopefully a sensible default int force_stream_type = FALSE; int video_stream = -1; int audio_stream = -1; int want_ac3_audio = FALSE; int input_is_dvd = TRUE; int want_dolby_as_dvb = TRUE; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-help",argv[ii]) || !strcmp("-h",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-avc",argv[ii]) || !strcmp("-h264",argv[ii])) { force_stream_type = TRUE; video_type = VIDEO_H264; } else if (!strcmp("-h262",argv[ii])) { force_stream_type = TRUE; video_type = VIDEO_H262; } else if (!strcmp("-vtype",argv[ii])) { CHECKARG("ps2ts",ii); err = int_value("ps2ts",argv[ii],argv[ii+1],TRUE,0, &video_type); if (err) return 1; ii++; force_stream_type = TRUE; } else if (!strcmp("-mp42",argv[ii])) { force_stream_type = TRUE; video_type = VIDEO_MPEG4_PART2; } else if (!strcmp("-dolby",argv[ii])) { CHECKARG("ps2ts",ii); if (!strcmp("dvb",argv[ii+1])) want_dolby_as_dvb = TRUE; else if (!strcmp("atsc",argv[ii+1])) want_dolby_as_dvb = FALSE; else { print_err("### ps2ts: -dolby must be followed by dvb or atsc\n"); return 1; } ii++; } else if (!strcmp("-stdin",argv[ii])) { had_input_name = TRUE; // more or less use_stdin = TRUE; } else if (!strcmp("-stdout",argv[ii])) { had_output_name = TRUE; // more or less use_stdout = TRUE; redirect_output_stderr(); } else if (!strcmp("-err",argv[ii])) { CHECKARG("ps2ts",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### ps2ts: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-dvd",argv[ii])) { input_is_dvd = TRUE; } else if (!strcmp("-notdvd",argv[ii]) || !strcmp("-nodvd",argv[ii])) { input_is_dvd = FALSE; } else if (!strcmp("-host",argv[ii])) { CHECKARG("ps2ts",ii); err = host_value("ps2ts",argv[ii],argv[ii+1],&output_name,&port); if (err) return 1; had_output_name = TRUE; // more or less use_tcpip = TRUE; ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("ps2ts",ii); err = int_value("ps2ts",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-prepeat",argv[ii])) { CHECKARG("ps2ts",ii); err = int_value("ps2ts",argv[ii],argv[ii+1],TRUE,10, &repeat_program_every); if (err) return 1; ii++; } else if (!strcmp("-pad",argv[ii])) { CHECKARG("ps2ts",ii); err = int_value("ps2ts",argv[ii],argv[ii+1],TRUE,10,&pad_start); if (err) return 1; ii++; } else if (!strcmp("-vpid",argv[ii])) { CHECKARG("ps2ts",ii); err = unsigned_value("ps2ts",argv[ii],argv[ii+1],0,&video_pid); if (err) return 1; ii++; } else if (!strcmp("-apid",argv[ii])) { CHECKARG("ps2ts",ii); err = unsigned_value("ps2ts",argv[ii],argv[ii+1],0,&audio_pid); if (err) return 1; ii++; } else if (!strcmp("-pmt",argv[ii])) { CHECKARG("ps2ts",ii); err = unsigned_value("ps2ts",argv[ii],argv[ii+1],0,&pmt_pid); if (err) return 1; ii++; } else if (!strcmp("-noaudio",argv[ii])) { keep_audio = FALSE; } else if (!strcmp("-vstream",argv[ii])) { CHECKARG("ps2ts",ii); err = int_value_in_range("ps2ts",argv[ii],argv[ii+1],0,0xF,0, &video_stream); if (err) return 1; ii++; } else if (!strcmp("-astream",argv[ii])) { CHECKARG("ps2ts",ii); err = int_value_in_range("ps2ts",argv[ii],argv[ii+1],0,0x1F,0, &audio_stream); if (err) return 1; want_ac3_audio = FALSE; ii++; } else if (!strcmp("-ac3stream",argv[ii])) { CHECKARG("ps2ts",ii); err = int_value_in_range("ps2ts",argv[ii],argv[ii+1],0,0x7,0, &audio_stream); if (err) return 1; want_ac3_audio = TRUE; input_is_dvd = TRUE; ii++; } else { fprint_err("### ps2ts: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name && had_output_name) { fprint_err("### ps2ts: Unexpected '%s'\n",argv[ii]); return 1; } else if (had_input_name) { output_name = argv[ii]; had_output_name = TRUE; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### ps2ts: No input file specified\n"); return 1; } if (!had_output_name) { print_err("### ps2ts: No output file specified\n"); return 1; } // Try to stop extraneous data ending up in our output stream if (use_stdout) { verbose = FALSE; quiet = TRUE; } err = open_PS_file(input_name,quiet,&ps); if (err) { fprint_err("### ps2ts: Unable to open input %s\n", (use_stdin?"":input_name)); return 1; } if (!quiet) fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); // Try to decide what sort of data stream we have if (force_stream_type || use_stdin) { if (!quiet) fprint_msg("Reading input as %s (0x%02x)\n", h222_stream_type_str(video_type),video_type); } else { err = determine_PS_video_type(ps,&video_type); if (err) return 1; if (!quiet) fprint_msg("Video appears to be %s (0x%02x)\n", h222_stream_type_str(video_type),video_type); } if (!quiet) { if (input_is_dvd) print_msg("Treating input as from DVD\n"); else print_msg("Treating input as NOT from DVD\n"); print_msg("Reading video from "); if (video_stream == -1) print_msg("first stream found"); else fprint_msg("stream %0#x (%d)",video_stream,video_stream); if (keep_audio) { print_msg(", audio from "); if (audio_stream == -1) fprint_msg("first %s found",(want_ac3_audio?"AC3 stream":"stream")); else fprint_msg("%s %0#x (%d)",(want_ac3_audio?"AC3 stream":"stream"), audio_stream,audio_stream); print_msg("\n"); } fprint_msg("Writing video with PID 0x%02x",video_pid); if (keep_audio) fprint_msg(", audio with PID 0x%02x,",audio_pid); fprint_msg(" PMT PID 0x%02x, PCR PID 0x%02x\n",pmt_pid,pcr_pid); if (max) fprint_msg("Stopping after %d program stream packets\n",max); } if (use_stdout) err = tswrite_open(TS_W_STDOUT,NULL,NULL,0,quiet,&output); else if (use_tcpip) err = tswrite_open(TS_W_TCP,output_name,NULL,port,quiet,&output); else err = tswrite_open(TS_W_FILE,output_name,NULL,0,quiet,&output); if (err) { fprint_err("### ps2ts: Unable to open %s\n",output_name); (void) close_PS_file(&ps); return 1; } err = ps_to_ts(ps,output,pad_start,repeat_program_every, video_type,input_is_dvd, video_stream,audio_stream,want_ac3_audio, want_dolby_as_dvb,pmt_pid,pcr_pid,video_pid, keep_audio,audio_pid,max,verbose,quiet); if (err) { print_err("### ps2ts: Error transferring data\n"); (void) close_PS_file(&ps); (void) tswrite_close(output,TRUE); return 1; } // And tidy up when we're finished err = tswrite_close(output,quiet); if (err) fprint_err("### ps2ts: Error closing output %s: %s\n",output_name, strerror(errno)); err = close_PS_file(&ps); if (err) fprint_err("### ps2ts: Error closing input %s\n", (use_stdin?"":input_name)); return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ps_defns.h000066400000000000000000000127231261471605300166470ustar00rootroot00000000000000/* * Datastructures for working with H.222 Program Stream packets - in * particular, for reading PES packets. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _ps_defns #define _ps_defns #include "compat.h" #include "h222_defns.h" #include "tswrite_defns.h" // ------------------------------------------------------------ // A program stream context, used to read PS and manage a read-ahead cache #define PS_READ_AHEAD_SIZE 5000 // The number of bytes to read ahead struct ps_reader { int input; // where we're reading from offset_t start; // the offset at which our data starts byte data[PS_READ_AHEAD_SIZE]; offset_t data_posn; // location of this data in the file int32_t data_len; // actual number of bytes in the buffer byte *data_end; // off the end of `data` byte *data_ptr; // which byte we're interested in (next) }; typedef struct ps_reader *PS_reader_p; #define SIZEOF_PS_READER sizeof(struct ps_reader) // ------------------------------------------------------------ // A program stream pack header (not including the system header packets) struct PS_pack_header { int id; // A number to identify this packet byte data[10]; // The data excluding the leading 00 00 01 BA uint64_t scr; // Formed from scr_base and scr_ext uint64_t scr_base; uint32_t scr_extn; uint32_t program_mux_rate; int pack_stuffing_length; }; typedef struct PS_pack_header *PS_pack_header_p; #define SIZEOF_PS_PACK_HEADER sizeof(struct PS_pack_header) // ------------------------------------------------------------ // A program stream packet (specifically one that starts with six bytes // organised as 00 00 01 ) struct PS_packet { int id; // A number to identify this packet byte *data; // The data including the leading 00 00 01 int data_len; // Its length byte stream_id; // Its stream id (i.e., data[4]) int packet_length; // The packet length (6 less than data_len) }; typedef struct PS_packet *PS_packet_p; #define SIZEOF_PS_PACKET sizeof(struct PS_packet) // ------------------------------------------------------------ // Number of streams of various types #define NUMBER_VIDEO_STREAMS 0x0F #define NUMBER_AUDIO_STREAMS 0x1F #define NUMBER_AC3_SUBSTREAMS 0x08 // DVD private_stream_1 substream identifiers // (also used for non-DVD data when we have identified the private data // appropriately) #define SUBSTREAM_OTHER 0 #define SUBSTREAM_AC3 1 // AC-3 audio (Dolby 5.1) #define SUBSTREAM_DTS 2 // DTS audio #define SUBSTREAM_LPCM 3 // LPCM audio (CD audio) #define SUBSTREAM_SUBPICTURES 4 // Sub-pictures #define SUBSTREAM_ERROR 5 // Error in deciding #define NUMBER_SUBSTREAM_TYPES 6 // useful for array sizing #define SUBSTREAM_STR(what) ((what)==SUBSTREAM_OTHER?"other": \ (what)==SUBSTREAM_AC3 ?"AC3": \ (what)==SUBSTREAM_DTS ?"DTS": \ (what)==SUBSTREAM_LPCM ?"LPCM": \ (what)==SUBSTREAM_SUBPICTURES?"subpictures": \ "???") #define SUBSTREAM_IS_AUDIO(what) ((what)==SUBSTREAM_AC3|| \ (what)==SUBSTREAM_DTS|| \ (what)==SUBSTREAM_LPCM) #define BSMOD_STR(bsmod,acmod) \ ((bsmod)==0?"main audio service: complete main (CM)": \ (bsmod)==1?"main audio service: music & effects (ME)": \ (bsmod)==2?"associated service: visually impaired (VI)": \ (bsmod)==3?"associated service: hearing impaired (HI)": \ (bsmod)==4?"associated service: dialogue (D)": \ (bsmod)==5?"associated service: commentary (C)": \ (bsmod)==6?"associated service: emergency (E)": \ (bsmod)==7 && (acmod)==1?"associated service: voice over (VO)": \ (bsmod)==7 && (acmod)>=2 && (acmod)<=7?"main audio service: karaoke": \ "???") #define ACMOD_STR(acmod) ((acmod)==0?"1+1 Ch1,Ch2": \ (acmod)==1?"1/0 C": \ (acmod)==2?"2/0 L,R": \ (acmod)==3?"3/0 L,C,R": \ (acmod)==4?"2/1 L,R,S": \ (acmod)==5?"3/1 L,C,R,S": \ (acmod)==6?"2/2 L,R,SL,SR": \ (acmod)==7?"3/2 L,C,R,SL,SR":"???") #endif // _ps_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ps_fns.h000066400000000000000000000326711261471605300163420ustar00rootroot00000000000000/* * Functions for working with H.222 Program Stream packets - in particular, * for reading PES packets. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _ps_fns #define _ps_fns #include "compat.h" #include "h222_defns.h" #include "tswrite_defns.h" #include "ps_defns.h" // ============================================================ // Program stream reading functions // ============================================================ /* * Build a program stream context attached to an input file. This handles * read-ahead buffering for the PS. * * - `input` is the file stream to read from. * - If `quiet`, then don't report on ignored bytes at the start of the file * - `ps` is the new PS context * * Returns 0 if all goes well, 1 otherwise. */ extern int build_PS_reader(int input, int quiet, PS_reader_p *ps); /* * Tidy up the PS read-ahead context after we've finished with it. * * Specifically: * * - free the datastructure * - set `ps` to NULL * * Does not close the associated file. */ extern void free_PS_reader(PS_reader_p *ps); /* * Open a PS file for reading. * * - `name` is the name of the file * - If `quiet`, then don't report on ignored bytes at the start of the file * - `ps` is the new PS context * * Returns 0 if all goes well, 1 otherwise. */ extern int open_PS_file(char *name, int quiet, PS_reader_p *ps); /* * Close a PS file, and free the reader context * * Returns 0 if all goes well, 1 otherwise. */ extern int close_PS_file(PS_reader_p *ps); /* * Given a program stream, attempt to determine if it holds H.262 or H.264 * data. * * Leaves the PS rewound to its "start". * * NOTE: It is probably better to use determine_PS_video_type(). * * - `ps` is the program stream to check (assumed just to have been * opened/built). This cannot be standard input, as it must be * seekable. * - `is_h264` is the result * * Returns 0 if all goes well, 1 if there was an error (including the * stream not appearing to be either). */ extern int determine_if_PS_is_h264(PS_reader_p ps, int *is_h264); /* * Given a program stream, attempt to determine what type of video data it * contains. * * Leaves the PS rewound to its "start". * * - `ps` is the program stream to check (assumed just to have been * opened/built). This cannot be standard input, as it must be * seekable. * - `video_type` is the result. Calls determine_PES_video_type(). * * Returns 0 if all goes well, 1 if there was an error (including the * stream not appearing to be either). */ extern int determine_PS_video_type(PS_reader_p ps, int *video_type); /* * Seek within the PS file. * * Note that if the intent is to *rewind* to the start of the PS data, * then `rewind_program_stream` should be used instead, as offset 0 is * not necessarily the same as the start of the program stream. * * - `ps` is the PS read-ahead context * - `posn` is the file offset to seek to * * Returns 0 if all goes well, 1 if something goes wrong */ extern int seek_using_PS_reader(PS_reader_p ps, offset_t posn); /* * Rewind the PS context to the remembered "start of data" * * Returns 0 if all goes well, 1 if something goes wrong */ extern int rewind_program_stream(PS_reader_p ps); /* * Print out a stream id in a manner consistent with the PS usages * of the stream id values. */ extern void print_stream_id(int is_msg, byte stream_id); /* * Look for the start (the first 4 bytes) of the next program stream packet. * * Assumes that (for some reason) alignment has been lost, and thus it is * necessary to scan forwards to find the next 00 00 01 prefix. * * Otherwise equivalent to a call of `read_PS_packet_start`. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then we want to explain what we're doing * - if `max` is non-zero, then it is the maximum number of bytes * to scan before giving up. * - `posn` is the file offset of the start of the packet * - `stream_id` is the identifying byte, after the 00 00 01 prefix. Note * that this is set correctly if MPEG_program_end_code was read, and is * 0 if an error occurred. * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, or * * 1 if some error (including the first 3 bytes not being 00 00 01) occurs. */ extern int find_PS_packet_start(PS_reader_p ps, int verbose, uint32_t max, offset_t *posn, byte *stream_id); /* * Look for the next PS pack header. * * Equivalent to calling `find_PS_packet_start` until `stream_id` is 0xBA * (in other words, equivalent to having read the pack header start with * `read_PS_packet_start`). * * If you want to call `read_PS_packet_start` to read this pack header start * in again, then call ``seek_using_PS_reader(ps,posn)`` to reposition ready * to read it. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then the 00 00 01 XX sequences found will be logged * to stderr, indicating the progress of our search * - if `max` is non-zero, then it is the maximum number of bytes * to scan before giving up. * - `posn` is the file offset of the start of the packet found * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, or * * 1 if some error (including the first 3 bytes not being 00 00 01) occurs. */ extern int find_PS_pack_header_start(PS_reader_p ps, int verbose, uint32_t max, offset_t *posn); /* * Read in (the rest of) a PS packet according to its length. * * Suitable for use reading PS PES packets and PS system header packets. * * NOTE that the `data` buffer in the `packet` is realloc'ed by this * function. It is thus important to ensure that the `packet` datastructure * contains a NULL pointer for said buffer before the first call of this * function. * * - `ps` is the PS read-ahead context we're reading from * - `stream_id` identifies what sort of packet it is * - `packet` is the packet we're reading the PES packet into. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int read_PS_packet_body(PS_reader_p ps, byte stream_id, PS_packet_p packet); /* * Read in the body of the pack header (but *not* the system header packets * therein). * * - `ps` is the PS read-ahead context we're reading from * - `hdr` is the packet we've read * * Returns 0 if it succeeds, or 1 if some error occurs. */ extern int read_PS_pack_header_body(PS_reader_p ps, PS_pack_header_p hdr); /* * Clear the contents of a PS packet datastructure. Frees the internal * `data` array. */ extern void clear_PS_packet(PS_packet_p packet); /* * Tidy up and free a PS packet datastructure after we've finished with it. * * Empties the PS packet datastructure, frees it, and sets `unit` to NULL. * * If `unit` is already NULL, does nothing. */ extern void free_PS_packet(PS_packet_p *packet); /* * Read in the start (the first 4 bytes) of the next program stream packet. * * If the bytes read don't appear to be valid (i.e., they do not start with * the 00 00 01 prefix), then the next pack header will be sought and read in. * * Note that sequences of 00 bytes before the 00 00 01 will be ignored. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then we want to explain what we're doing * - `posn` is the file offset of the start of the packet * - `stream_id` is the identifying byte, after the 00 00 01 prefix. Note * that this is set correctly if MPEG_program_end_code was read, and is * 0 if an error occurred or reading was ended because `max` packets had * been read. * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, * * 2 if the bytes read are not 00 00 01 `stream_id`, or * * 1 if some other error occurs. */ extern int read_PS_packet_start(PS_reader_p ps, int verbose, offset_t *posn, byte *stream_id); /* * Inspect the given PS packet, and determine if it contains AC3 or DTS audio data. * * - `packet` is the packet's data, already established as private_data_1 * - `is_dvd` is true if the data should be interpreted as DVD data * - if `verbose`, report on the details of what we find out * - `substream_index` returns the substream's index, taken from the low * nibble of the substream id, and adjusted to start at 0. This will be * a value in the range 0-7 for DTS, AC3 and LPCM, and in the range 0-1F * (0-31) for subpictures. * - for AC3, `bsmod` and `acmod` return the appropriate quantities, * otherwise they are 0. * * Returns one of the SUBSTREAM_* values. */ extern int identify_private1_data(struct PS_packet *packet, int is_dvd, int verbose, int *substream_index, byte *bsmod, byte *acmod); // ============================================================ // PS to TS functions // ============================================================ /* * Read program stream and write transport stream * * - `ps` is the program stream * - `output` is the transport stream * - `pad_start` is the number of filler TS packets to start the output * with. * - `program_repeat` is how often (after how many PS packs) to repeat * the program information (PAT/PMT) * - `video_type` indicates what type of video is being transferred. It should * be VIDEO_H264, VIDEO_H262, etc. * - `is_dvd` should be true if this input represents DVD data; i.e., with * private_stream_1 used for AC-3/DTS/etc., and with substream headers * therein. * - `video_stream` indicates which video stream we want - i.e., the stream * with id 0xE0 + . -1 means the first encountered. * - `audio_stream` indicates which audio stream we want. If `want_ac3_audio` * is false, then this will be the stream with id 0xC0 + , * or -1 for the first audio stream encountered. * - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want * audio from private_stream_1 (0xBD) with substream id , * otherwise we ignore `audio_stream` and assume that all data in * private_stream_1 is the audio we want. * - `dolby_is_dvb` should be true if Dolby (AC-3) audio (if selected) should * be output using the DVB stream type 0x06, false if using the ATSC stream * type 0x81. This is ignored if the audio being output is not Dolby. * - `pmt_pid` is the PID of the PMT to write * - `pcr_pid` is the PID of the TS unit containing the PCR * - `video_pid` is the PID for the video we write * - `keep_audio` is true if the audio stream should be output, false if * it should be ignored * - `audio_pid` is the PID for the audio we write * - if `max` is non-zero, then we want to stop reading after we've read * `max` packs * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ extern int ps_to_ts(PS_reader_p ps, TS_writer_p output, int pad_start, int program_repeat, int video_type, int is_dvd, int video_stream, int audio_stream, int want_ac3_audio, int dolby_is_dvb, uint32_t pmt_pid, uint32_t pcr_pid, uint32_t video_pid, int keep_audio, uint32_t audio_pid, int max, int verbose, int quiet); #endif // _ps_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/psdots.c000066400000000000000000000234751261471605300163630ustar00rootroot00000000000000/* * Report on the content of an H.222 program stream (PS) file as a sequence * of single characters, representing appropriate entities. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ps_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" /* * Report on the given file with characters representing packets * * - `ps` is the PS file we're reading * - if `max` is more than zero, then it is the maximum number of PS packs * we want to read * - `verbose` is true if we want an explanation of the characters * * Returns 0 if all went well, 1 if something went wrong. */ static int report_ps_dots(PS_reader_p ps, int max, int verbose) { int err; int count = 0; int num_packs = 0; offset_t posn; // The location in the input file of the current packet byte stream_id; // The packet's stream id int end_of_file = FALSE; struct PS_packet packet = {0}; struct PS_pack_header header = {0}; if (verbose) print_msg("Characters represent the following:\n" " [ Pack header\n" " H System header\n" " ] MPEG_program_end_code\n" " p Private stream (1 or 2)\n" " v Video stream 0\n" " v Video stream (>0)\n" " a Audio stream 0\n" " a Audio stream (>0)\n" " M Program stream map\n" " D Program stream directory\n" " . Padding\n" " ? Something else\n" ); // Read the start of the first packet (we confidently expect this // to be a pack header) err = read_PS_packet_start(ps,FALSE,&posn,&stream_id); if (err == EOF) { print_err("### Error reading first pack header\n"); print_err(" Unexpected end of PS at start of stream\n"); return 1; } else if (err) { print_err("### Error reading first pack header\n"); return 1; } if (stream_id != 0xba) { print_err("### Program stream does not start with pack header\n"); fprint_err(" First packet has stream id %02X (",stream_id); print_stream_id(FALSE,stream_id); print_err(")\n"); return 1; } // But given that, we can now happily loop reading in packs for (;;) { int num_system_headers = 0; if (max > 0 && num_packs >= max) { fprint_msg("\nStopping after %d packs\n",num_packs); return 0; } num_packs ++; print_msg("["); fflush(stdout); err = read_PS_pack_header_body(ps,&header); if (err) { fprint_err("### Error reading data for pack header starting at " OFFSET_T_FORMAT "\n",posn); return 1; } // Read (and, for the moment, at least, ignore) any system headers for (;;) { err = read_PS_packet_start(ps,FALSE,&posn,&stream_id); if (err == EOF) { end_of_file = TRUE; if (stream_id == 0xB9) { print_msg("]"); fflush(stdout); } break; } else if (err) return 1; if (stream_id == 0xbb) // System header { print_msg("H"); fflush(stdout); err = read_PS_packet_body(ps,stream_id,&packet); if (err) { fprint_err("### Error reading system header starting at " OFFSET_T_FORMAT "\n",posn); return 1; } // For the moment, just ignore the system header content num_system_headers ++; } else break; } if (end_of_file) break; // We've finished with system headers - onto data (one fondly hopes) for (;;) { if (stream_id == 0xba) // Start of the next pack break; if (stream_id == 0xBC) print_msg("M"); else if (stream_id == 0xFF) print_msg("D"); else if (stream_id == 0xBD) print_msg("p1"); else if (stream_id == 0xBE) print_msg("."); else if (stream_id == 0xBF) print_msg("p2"); else if (stream_id >= 0xC0 && stream_id <=0xDF) { int number = stream_id & 0x1F; if (number == 0) print_msg("a"); else fprint_msg("a%x",number); } else if (stream_id >= 0xE0 && stream_id <= 0xEF) { int number = stream_id & 0x0F; if (number == 0) print_msg("v"); else fprint_msg("v%x",number); } else print_msg("?"); fflush(stdout); err = read_PS_packet_body(ps,stream_id,&packet); if (err) { fprint_err("### Error reading PS packet starting at " OFFSET_T_FORMAT "\n",posn); return 1; } err = read_PS_packet_start(ps,FALSE,&posn,&stream_id); if (err == EOF) { if (stream_id == 0xB9) { print_msg("]"); fflush(stdout); } end_of_file = TRUE; break; } else if (err) return 1; } if (end_of_file) break; } clear_PS_packet(&packet); fprint_msg("\nRead %d PS packet%s in %d pack%s\n", count,(count==1?"":"s"), num_packs,(num_packs==1?"":"s")); return 0; } static void print_usage() { print_msg( "Usage: psdots [switches] []\n" "\n" ); REPORT_VERSION("psdots"); print_msg( "\n" " Present the content of a Program Stream file as a sequence of\n" " characters, representing the packets.\n" "\n" "Files:\n" " is an H.222 Program Stream file (but see -stdin)\n" "\n" "Switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Input from standard input, instead of a file\n" " -verbose, -v Output a description of the characters used\n" " -max , -m Maximum number of PS packets to read\n" ); } int main(int argc, char **argv) { int use_stdin = FALSE; char *input_name = NULL; int had_input_name = FALSE; PS_reader_p ps; // The PS file we're reading int max = 0; // The maximum number of PS packets to read (or 0) int verbose = FALSE; // True => output diagnostic/progress messages int err = 0; int ii = 1; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-err",argv[ii])) { CHECKARG("psdots",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### psdots: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("psdots",ii); err = int_value("psdots",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-stdin",argv[ii])) { use_stdin = TRUE; had_input_name = TRUE; // so to speak } else { fprint_err("### psdots: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name) { fprint_err("### psdots: Unexpected '%s'\n",argv[ii]); return 1; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### psdots: No input file specified\n"); return 1; } err = open_PS_file(input_name,FALSE,&ps); if (err) { fprint_err("### psdots: Unable to open input file %s\n", (use_stdin?"":input_name)); return 1; } fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); if (max) fprint_msg("Stopping after %d PS packets\n",max); err = report_ps_dots(ps,max,verbose); if (err) print_err("### psdots: Error reporting on input stream\n"); err = close_PS_file(&ps); if (err) { fprint_err("### psdots: Error closing input file %s\n", (use_stdin?"":input_name)); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/psreport.c000066400000000000000000000460151261471605300167200ustar00rootroot00000000000000/* * Report on an H.222 program stream (PS) file. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ps_fns.h" #include "pes_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" /* * Report on the given file * * - `ps` represents the PS file we're reading * - if `is_dvd` is TRUE, then assume that data in private_stream_1 is * stored using the DVD "substream" convention * - if `max` is more than zero, then it is the maximum number of PS packs * we want to read * - if `verbose` is true, then we want reporting on each packet, * otherwise just a summary of the number of packs/packets is output. * * Returns 0 if all went well, 1 if something went wrong. */ static int report_ps(PS_reader_p ps, int is_dvd, int max, int verbose) { int err; offset_t posn = 0; // The location in the input file of the current packet byte stream_id; // The packet's stream id int end_of_file = FALSE; struct PS_packet packet = {0}; struct PS_pack_header header = {0}; // Summary data int count = 0; int num_packs = 0; int num_maps = 0; int num_dirs = 0; int num_video[NUMBER_VIDEO_STREAMS]; int min_video_size[NUMBER_VIDEO_STREAMS]; int max_video_size[NUMBER_VIDEO_STREAMS]; double sum_video_size[NUMBER_VIDEO_STREAMS]; int num_audio[NUMBER_AUDIO_STREAMS]; int min_audio_size[NUMBER_AUDIO_STREAMS]; int max_audio_size[NUMBER_AUDIO_STREAMS]; double sum_audio_size[NUMBER_AUDIO_STREAMS]; #define cPRIVATE1 0 #define cPRIVATE2 1 #define cPRIVATE_SIZE 2 int num_private[cPRIVATE_SIZE] = {0, 0}; int min_private_size[cPRIVATE_SIZE] = {INT_MAX, INT_MAX}; int max_private_size[cPRIVATE_SIZE] = {0, 0}; double sum_private_size[cPRIVATE_SIZE] = {0, 0}; #define cAC3 0 #define cDTS 1 #define cLPCM 2 #define cSUBPICTURES 3 #define cOTHER 4 // Our arrays are 5 wide (for the 4 types of data + other we know about) by // deep, where =32 allows for 32 subpictures. This wastes space // for the other datatypes, which can only go to 8, but is simple... #define cSIZE 5 #define cDEPTH 32 int num_other[cSIZE][cDEPTH]; int min_other_size[cSIZE][cDEPTH]; int max_other_size[cSIZE][cDEPTH]; double sum_other_size[cSIZE][cDEPTH]; // AC3 data can have two other types of information we want to remember... byte ac3_bsmod[cDEPTH] = {0}; byte ac3_acmod[cDEPTH] = {0}; int ii,jj; for (jj=0; jj 0 && num_packs >= max) { if (verbose) fprint_msg("Stopping after %d packs\n",num_packs); break; } num_packs ++; err = read_PS_pack_header_body(ps,&header); if (err) { fprint_err("### Error reading data for pack header starting at " OFFSET_T_FORMAT "\n",posn); goto give_up; } if (verbose) fprint_msg("\n" OFFSET_T_FORMAT_08 ": Pack header: SCR " LLD_FORMAT " (" LLD_FORMAT "/%d) mux rate %d\n",posn,header.scr,header.scr_base, header.scr_extn,header.program_mux_rate); // Read (and, for the moment, at least, ignore) any system headers for (;;) { err = read_PS_packet_start(ps,verbose,&posn,&stream_id); if (err == EOF) { end_of_file = TRUE; break; } else if (err) goto give_up; count++; if (stream_id == 0xbb) // System header { err = read_PS_packet_body(ps,stream_id,&packet); if (err) { fprint_err("### Error reading system header starting at " OFFSET_T_FORMAT "\n",posn); goto give_up; } // For the moment, just ignore the system header content num_system_headers ++; if (verbose) fprint_msg(OFFSET_T_FORMAT_08 ": System header %d\n", posn,num_system_headers); } else break; } if (end_of_file) break; // We've finished with system headers - onto data (one fondly hopes) for (;;) { if (stream_id == 0xba) // Start of the next pack break; err = read_PS_packet_body(ps,stream_id,&packet); if (err) { fprint_err("### Error reading PS packet starting at " OFFSET_T_FORMAT "\n",posn); goto give_up; } // For the moment, just ignore its content if (verbose) { fprint_msg(OFFSET_T_FORMAT_08 ": PS Packet %2d stream %02X (", posn,count,stream_id); print_stream_id(TRUE,stream_id); print_msg(")\n"); print_data(TRUE," Packet", packet.data,packet.data_len,20); #if 1 // XXX print_end_of_data(" ",packet.data,packet.data_len,20); #endif if (IS_AUDIO_STREAM_ID(stream_id) || IS_VIDEO_STREAM_ID(stream_id)) #if 1 // XXX report_PES_data_array2(-1,packet.data,packet.data_len,20); #else report_PES_data_array(" ",packet.data,packet.data_len,TRUE); #endif } if (stream_id == 0xBC) num_maps ++; else if (stream_id == 0xFF) num_dirs ++; else if (stream_id == PRIVATE1_AUDIO_STREAM_ID) { int substream_index; byte bsmod, acmod; int what = identify_private1_data(&packet,is_dvd,verbose, &substream_index,&bsmod,&acmod); num_private[cPRIVATE1] ++; sum_private_size[cPRIVATE1] += packet.data_len; if (packet.data_len > max_private_size[cPRIVATE1]) max_private_size[cPRIVATE1] = packet.data_len; if (packet.data_len < min_private_size[cPRIVATE1]) min_private_size[cPRIVATE1] = packet.data_len; if (what != SUBSTREAM_ERROR) { int index; if (substream_index < 0 || substream_index >= cDEPTH) { fprint_err("Internal error: got substream index %d" " (instead, counting item wrongly as index %d)\n", substream_index,cDEPTH-1); substream_index = cDEPTH-1; } switch (what) { case SUBSTREAM_AC3: index = cAC3; ac3_bsmod[substream_index] = bsmod; ac3_acmod[substream_index] = acmod; break; case SUBSTREAM_DTS: index = cDTS; break; case SUBSTREAM_LPCM: index = cLPCM; break; case SUBSTREAM_SUBPICTURES: index = cSUBPICTURES; break; case SUBSTREAM_OTHER: index = cOTHER; break; default: fprint_err("Internal error: got substream id %d" " (instead, counting item wrongly as OTHER)\n",what); index = cOTHER; break; } num_other[index][substream_index] ++; sum_other_size[index][substream_index] += packet.data_len; if (packet.data_len > max_other_size[index][substream_index]) max_other_size[index][substream_index] = packet.data_len; if (packet.data_len < min_other_size[index][substream_index]) min_other_size[index][substream_index] = packet.data_len; } } else if (stream_id == PRIVATE2_AUDIO_STREAM_ID) { num_private[cPRIVATE2] ++; sum_private_size[cPRIVATE2] += packet.data_len; if (packet.data_len > max_private_size[cPRIVATE2]) max_private_size[cPRIVATE2] = packet.data_len; if (packet.data_len < min_private_size[cPRIVATE2]) min_private_size[cPRIVATE2] = packet.data_len; } else if (IS_AUDIO_STREAM_ID(stream_id)) { int num = stream_id & 0x1F; num_audio[num] ++; sum_audio_size[num] += packet.data_len; if (packet.data_len > max_audio_size[num]) max_audio_size[num] = packet.data_len; if (packet.data_len < min_audio_size[num]) min_audio_size[num] = packet.data_len; } else if (IS_VIDEO_STREAM_ID(stream_id)) { int num = stream_id & 0x0F; num_video[num] ++; sum_video_size[num] += packet.data_len; if (packet.data_len > max_video_size[num]) max_video_size[num] = packet.data_len; if (packet.data_len < min_video_size[num]) min_video_size[num] = packet.data_len; } err = read_PS_packet_start(ps,verbose,&posn,&stream_id); if (err == EOF) { end_of_file = TRUE; break; } else if (err) goto give_up; count++; } if (end_of_file) break; } give_up: clear_PS_packet(&packet); { int ii; fprint_msg("Packets (total): %8d\n",count); fprint_msg("Packs: %8d\n",num_packs); for (ii=0; ii 0) { fprint_msg("Video packets (stream %2d): %8d",ii,num_video[ii]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_video_size[ii],max_video_size[ii], sum_video_size[ii]/num_video[ii]); } for (ii=0; ii 0) { fprint_msg("Audio packets (stream %2d): %8d",ii,num_audio[ii]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_audio_size[ii],max_audio_size[ii], sum_audio_size[ii]/num_audio[ii]); } if (num_private[cPRIVATE1] > 0) { int ii; fprint_msg("Private1 packets: %8d",num_private[cPRIVATE1]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_private_size[cPRIVATE1],max_private_size[cPRIVATE1], sum_private_size[cPRIVATE1]/num_private[cPRIVATE1]); for (ii=0; ii 0) { fprint_msg(" AC3, index %2d: %8d",ii,num_other[cAC3][ii]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_other_size[cAC3][ii],max_other_size[cAC3][ii], sum_other_size[cAC3][ii]/num_other[cAC3][ii]); fprint_msg(" %s\n", BSMOD_STR(ac3_bsmod[ii],ac3_acmod[ii])); fprint_msg(" audio coding mode %s\n", ACMOD_STR(ac3_acmod[ii])); } } for (ii=0; ii 0) { fprint_msg(" DTS, index %2d: %8d",ii,num_other[cDTS][ii]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_other_size[cDTS][ii],max_other_size[cDTS][ii], sum_other_size[cDTS][ii]/num_other[cDTS][ii]); } } for (ii=0; ii 0) { fprint_msg(" LPCM, index %2d: %8d",ii,num_other[cLPCM][ii]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_other_size[cLPCM][ii],max_other_size[cLPCM][ii], sum_other_size[cLPCM][ii]/num_other[cLPCM][ii]); } } for (ii=0; ii 0) { fprint_msg(" SUBPICTURES, index %2d: %8d",ii,num_other[cSUBPICTURES][ii]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_other_size[cSUBPICTURES][ii],max_other_size[cSUBPICTURES][ii], sum_other_size[cSUBPICTURES][ii]/num_other[cSUBPICTURES][ii]); } } for (ii=0; ii 0) { fprint_msg(" OTHER, index %2d: %8d",ii,num_other[cOTHER][ii]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_other_size[cOTHER][ii],max_other_size[cOTHER][ii], sum_other_size[cOTHER][ii]/num_other[cOTHER][ii]); } } } if (num_private[cPRIVATE2] > 0) { fprint_msg("Private2 packets: %8d",num_private[cPRIVATE2]); fprint_msg(" min size %5d, max size %5d, mean size %7.1f\n", min_private_size[cPRIVATE2],max_private_size[cPRIVATE2], sum_private_size[cPRIVATE2]/num_private[cPRIVATE2]); } fprint_msg("Program stream maps: %8d\n",num_maps); fprint_msg("Program stream directories: %8d\n",num_maps); } return 0; } static void print_usage() { print_msg( "Usage: psreport [switches] []\n" "\n" ); REPORT_VERSION("psreport"); print_msg( "\n" " Report on the packets in a Program Stream.\n" "\n" "Files:\n" " is an H.222 Program Stream file (but see -stdin)\n" "\n" "Switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Input from standard input, instead of a file\n" " -verbose, -v Output packet data as well.\n" " -max , -m Maximum number of PS packets to read\n" " -dvd The PS data is from a DVD. This is the default.\n" " This switch has no effect on MPEG-1 PS data.\n" " -notdvd, -nodvd The PS data is not from a DVD.\n" " The DVD specification stores AC-3 (Dolby), DTS and\n" " other audio in a specialised manner in private_stream_1.\n" ); } int main(int argc, char **argv) { int use_stdin = FALSE; char *input_name = NULL; int had_input_name = FALSE; PS_reader_p ps; // The PS file we're reading int max = 0; // The maximum number of PS packets to read (or 0) int verbose = FALSE; // True => output diagnostic/progress messages int is_dvd = TRUE; int err = 0; int ii = 1; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-err",argv[ii])) { CHECKARG("psreport",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### psreport: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; } else if (!strcmp("-dvd",argv[ii])) { is_dvd = TRUE; } else if (!strcmp("-notdvd",argv[ii]) || !strcmp("-nodvd",argv[ii])) { is_dvd = FALSE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("psreport",ii); err = int_value("psreport",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-stdin",argv[ii])) { use_stdin = TRUE; had_input_name = TRUE; // so to speak } else { fprint_err("### psreport: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name) { fprint_err("### psreport: Unexpected '%s'\n",argv[ii]); return 1; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### psreport: No input file specified\n"); return 1; } err = open_PS_file(input_name,FALSE,&ps); if (err) { fprint_err("### psreport: Unable to open input file %s\n", (use_stdin?"":input_name)); return 1; } fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); if (is_dvd) print_msg("Assuming data is from a DVD\n"); else print_msg("Assuming data is NOT from a DVD\n"); if (max) fprint_msg("Stopping after %d PS packets\n",max); err = report_ps(ps,is_dvd,max,verbose); if (err) print_err("### psreport: Error reporting on input stream\n"); err = close_PS_file(&ps); if (err) { fprint_err("### psreport: Error closing input file %s\n", (use_stdin?"":input_name)); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/python/000077500000000000000000000000001261471605300162115ustar00rootroot00000000000000tstools-1.13~git20151030/python/Makefile000066400000000000000000000031751261471605300176570ustar00rootroot00000000000000# This is a preliminary Makefile for experimentin with Pyrex builds # It assumes that the normal tstools Makefile has been used to build # the tools already. It also assumes that Pyrex is installed, and that # pyrexc is on the path. It probably assumes Linux or Mac OS/X as well... # Note: Building tstools.pyx needs at least Pyrex 0.9.7 (because I use the new # 'for' loop syntax, apart from anything else). On Ubuntu, this means # installing it "by hand", as the version packaged for Hardy Heron (as of # 27-Aug-2008, at least) is 0.9.6.4 # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tibs (tibs@berlios.de) # # ***** END LICENSE BLOCK ***** .PHONY: all setup test all: setup test .PHONY: setup setup: python Setup.py build_ext --inplace .PHONY: test test: setup ./rundoctest.py .PHONY: clean clean: python Setup.py clean -rm tstools/*.so .PHONY: distclean distclean: clean -rm tstools/*.c tstools-1.13~git20151030/python/Setup.py000066400000000000000000000025161261471605300176670ustar00rootroot00000000000000"""Setup.py -- for building tstools Pyrex modules """ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tibs (tibs@berlios.de) # # ***** END LICENSE BLOCK ***** from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext tstools = Extension("tstools.tstools", ['tstools/tstools.pyx'], include_dirs=['..'], library_dirs=['../lib'], libraries=['tstools'], ) setup( name = 'tstools', cmdclass = {'build_ext': build_ext}, ext_modules=[tstools] ) tstools-1.13~git20151030/python/rundoctest.py000077500000000000000000000032541261471605300207640ustar00rootroot00000000000000#! /usr/bin/env python """Run the doctest on a text file Usage: doctext.py [file] [file] defaults to ``test.txt`` """ import sys import doctest def main(): args = sys.argv[1:] filename = None verbose = False for word in args: if word in ("-v", "-verbose"): verbose = True elif word in ("-h", "-help", "/?", "/help", "--help"): print __doc__ return else: if filename: print "Filename '%s' already specified"%filename return else: filename = word if not filename: filename = "test.txt" print print 'Ignore any output lines starting ### or !!!. These are written by the' print 'underlying C library, and are not "seen" (or hidden) by doctest.' print # I want to be able to use the "with" statement in the doctests. # It's not possible to use "from __future__ import with_statement" # in doctests as such. Instead, one has to add the resulting globals # to the doctest context. Which seems to be done as follows: import __future__ extraglobs={'with_statement':__future__.with_statement} (failures,tests) = doctest.testfile(filename,verbose=verbose, extraglobs=extraglobs) testword = "test" if tests != 1: testword = "tests" failword = "failure" if failures != 1: failword = "failures" print print "File %s: %d %s, %d %s"%(filename,tests,testword,failures,failword) print if failures == 0: print 'The little light is GREEN' else: print 'The little light is RED' if __name__ == "__main__": main() tstools-1.13~git20151030/python/test.txt000066400000000000000000000546641261471605300177500ustar00rootroot00000000000000Some tests for the Python binding of the TS tools ================================================= Elementary streams -- basic functionality ----------------------------------------- In this context, we take an elementary stream to be MPEG-1, MPEG-2 or H.264 (MPEG-4 Part 10) ES data, as is assumed by the rest of tstools. We shall assume that our standard sample data has been downloaded and expanded into our sibling ``data`` directory. See the ``../data/setup.sh`` script. >>> test_es_file = '../data/ed24p_11.video.es' First, check we've got the basics working: >>> from tstools import ESFile >>> stream = ESFile(test_es_file) The filename is available as a "readonly" value: >>> stream.name == test_es_file True We've opened it for read: >>> stream.is_readable() == True True >>> stream.is_writable() == False True >>> stream.mode 'r' We should be able to iterate over its ES units: >>> count = 0 >>> es_unit_list = [] >>> for unit in stream: ... count += 1 ... print unit ... es_unit_list.append(unit) ... if count > 5: ... break ES unit: start code 00, len 9: 00 00 01 00 01 df ff fb b8 ES unit: start code b5, len 9: 00 00 01 b5 85 45 4b 5d 80 ES unit: start code 01, len 1645: 00 00 01 01 0a b0 10 09... ES unit: start code 02, len 1634: 00 00 01 02 0a b0 10 09... ES unit: start code 03, len 16: 00 00 01 03 0a b0 10 07... ES unit: start code 04, len 15: 00 00 01 04 0a b0 10 02... From ``hexdump -C`` I get (something that can be written out as):: 00 00 01 00 01 df ff fb b8 00 00 01 b5 85 45 4b 5d 80 00 00 01 01 0a b0 10 09 1c 56 ec d8 72 94 01 ... which tends to support those results. And close it: >>> stream.close() >>> stream.is_writable() == False True >>> stream.is_readable() == False True >>> stream.mode is None True We can ask an ES unit about itself: >>> unit = es_unit_list[0] >>> print unit.start_posn 0+0 >>> unit.start_code 0 >>> unit.PES_had_PTS 0 >>> data = unit.data >>> len(data) 9 >>> data[0] '\x00' >>> text = 'data:' >>> for ii in range(8): ... text += ' %02x'%ord(data[ii]) >>> print text data: 00 00 01 00 01 df ff fb >>> unit.fred Traceback (most recent call last): ... AttributeError >>> print repr(unit) ESUnit("\x00\x00\x01\x00\x01\xdf\xff\xfb\xb8") ES units can be compared for equality (but not order): >>> es_unit_list[0] == es_unit_list[0] 1 >>> es_unit_list[0] == es_unit_list[1] 0 >>> es_unit_list[0] != es_unit_list[1] 1 >>> es_unit_list[0] != es_unit_list[0] 0 >>> es_unit_list[0] < es_unit_list[1] Traceback (most recent call last): ... TypeError: ESUnit only supports == and != comparisons We can create an ES unit from a Python 'string': >>> from tstools import ESUnit >>> print 'old ',es_unit_list[0] old ES unit: start code 00, len 9: 00 00 01 00 01 df ff fb b8 >>> new = ESUnit(es_unit_list[0].data) >>> print 'new ',new new ES unit: start code 00, len 9: 00 00 01 00 01 df ff fb b8 >>> print 'old ',es_unit_list[0] old ES unit: start code 00, len 9: 00 00 01 00 01 df ff fb b8 >>> new == es_unit_list[0] True or even: >>> exec 'u = ' + repr(unit) >>> u == unit 1 And write another file... >>> import tempfile >>> import os >>> directory = tempfile.mkdtemp() >>> filename = os.path.join(directory,'tstools_test_1.es') >>> out = ESFile(filename,'w') >>> out.name == filename True >>> out.is_readable() == False True >>> out.is_writable() == True True >>> out.mode 'w' >>> for unit in es_unit_list: ... out.write(unit) >>> out.close() Did that do the right thing? Check that we can read the units back (one by one, to test the ``read`` method), and that the units we read back are identical to those we wrote. >>> infile = ESFile(filename,'r') >>> other_units = [] >>> for ii in range(0,6): ... other_units.append(infile.read()) >>> >>> for ii in range(0,6): ... if es_unit_list[ii] != other_units[ii]: ... print 'Error: unit %d does not match'%ii ... break We already saw an ESOffset being returned: >>> print es_unit_list[0].start_posn 0+0 >>> print es_unit_list[1].start_posn 9+0 >>> es_unit_list[1].start_posn.report() Offset 0 in packet at offset 9 in file >>> print repr(es_unit_list[1].start_posn) ESOffset(infile=9,inpacket=0) Output more like that produced by the C report tools can also be obtained: >>> print es_unit_list[1].start_posn.formatted() 00000000/00000009 We can create our own: >>> from tstools import ESOffset >>> offset = ESOffset(59,27) >>> offset.report() Offset 27 in packet at offset 59 in file >>> offset.infile 59L >>> offset.inpacket 27 There are keywords for the arguments, as well, for clarity: >>> ESOffset(infile=59,inpacket=27) == offset True And both default to 0: >>> ESOffset(infile=37).report() Offset 0 in packet at offset 37 in file >>> ESOffset().report() Offset 0 in packet at offset 0 in file Files can get very long -- the underlying ``lseek`` call can probably take 64-bit offsets. With luck, the Pyrex implementation can too: >>> loffset = ESOffset(0x1111111111111111,5) >>> loffset.report() Offset 5 in packet at offset 1229782938247303441 in file That's why we got the "59L" in an earlier example... And it may be useful to be able to compare them: >>> es_unit_list[1].start_posn > es_unit_list[0].start_posn True >>> es_unit_list[1].start_posn == es_unit_list[1].start_posn True >>> es_unit_list[0].start_posn < es_unit_list[1].start_posn True Seeking (to the start of an ES unit) is useful. However, the following are just testing the three ways of specifying the location to seek to (which may be overkill, but all seemed sensible at the time): >>> f = ESFile(test_es_file) >>> o = ESOffset(10,20) >>> print f.seek(o) 10+20 >>> print f.seek(20) 20+0 >>> print f.seek(30,40) 30+40 >>> print f.seek(10,20,30) Traceback (most recent call last): ... TypeError: Seek argument must be one integer, two integers or an ESOffset >>> print f.seek('fred') Traceback (most recent call last): ... TypeError: Seek argument must be one integer, two integers or an ESOffset >>> print f.seek(-1) Traceback (most recent call last): ... TSToolsException: Error seeking to (-1,) in file '../data/ed24p_11.video.es' Let's try proper seeking, though: >>> p = f.seek(es_unit_list[-1].start_posn) >>> p == es_unit_list[-1].start_posn True >>> u = f.read() >>> u.start_posn == es_unit_list[-1].start_posn True >>> u == es_unit_list[-1] True OK. So ``seek()`` returns a tuple of (infile,inpacket), whilst the ``start_posn`` attribute is an ``ESOffset``. Which may or may not make sense -- but I really would rather the latter had *some* annotation explicit as to what each field means, because it *is* impossible to decide rationally which order they should come in. The "with" syntax should also work: >>> count = 0 >>> with ESFile(test_es_file) as f: ... for unit in f: ... count += 1 ... print unit ... if count > 5: ... break ES unit: start code 00, len 9: 00 00 01 00 01 df ff fb b8 ES unit: start code b5, len 9: 00 00 01 b5 85 45 4b 5d 80 ES unit: start code 01, len 1645: 00 00 01 01 0a b0 10 09... ES unit: start code 02, len 1634: 00 00 01 02 0a b0 10 09... ES unit: start code 03, len 16: 00 00 01 03 0a b0 10 07... ES unit: start code 04, len 15: 00 00 01 04 0a b0 10 02... Transport streams -- basic functionality - reading -------------------------------------------------- Still using sample data, we can read from TS files: >>> test_ts_file = '../data/ed24p_11.ts' >>> from tstools import TSFile >>> tsfile = TSFile(test_ts_file) >>> tsfile.name == test_ts_file True >>> tsfile.mode == 'r' True >>> tsfile.is_readable() == True True >>> tsfile.is_writable() == False True And also: >>> tsfile.close() >>> TSFile('no-such-file.fred') Traceback (most recent call last): ... TSToolsException: Error opening file 'no-such-file.fred' for TS reading: No such file or directory Trusty hexdump suggests that the first few TS packets start:: 47 1f ff 10 ff ff ff ff ff ff ff ff ff ff ff ff i.e., they look are padding packets, with PID 0x1FFF. tsreport appears to show the first interesting TS packet occurring at 13348, which is 71*188:: 13348: TS Packet 72 PID 0032 [pusi] stream type not identified PES header Start code: 00 00 01 Stream ID: bd (189) SYSTEM START: Private stream 1 PES packet length: 0612 (1554) Flags: 84 80 data-aligned : PTS PES header len 15 PTS 54347445 Data (160 bytes): 0b 77 0d 73 1c 20 43 fe 21 06 a2 b8 60 75 dd 6f 87 ae 95 2c ee dc cf ae 95 5b b2 31 13 8c 56 fd 32 a5 4f a1 43 5e 95 f3 ea 6f 9e d7 75 0a 93 f5 4b 9f 4e 73 0d f2 a7 b4 df 53 54 82 1b e7 68 1f d2 7e 99 2a 97 ee 9e bf 7c a9 25 78 62 4e 7f 4a 2b a4 b4 dc d8 56 9d 31 18 4f a9 bf 7c a6 d5 c8 6a 95 3e 54 be 15 34 ca 61 42 7d 0d f2 a7 af 6c 3f 7c 43 5b e7 d5 ab b6 74 e5 fa 57 ce 9d a4 af 49 53 f4 a9 60 25 9c 95 fa 97 b7 12 99 6f 55 22 40 77 17 b2 e9 54 24 ad 59 e4 5d 2f 61 c4 36 ee That's 0x3424, and hexdump shows it staring:: 47 40 32 14 00 00 01 bd 06 12 84 80 0f 21 0c f5 8d 6b ff ff ff ff ff ff ff ff ff ff 0b 77 0d 73 1c 20 43 fe 21 06 a2 b8 60 75 Interpreted as: * ``47``: it's TS * ``40 32``: it's PID 0x32, with the PUSI bit set * ``14`` == ``0001.0100``: that ``0001`` means we have a payload only (no adaptation field), so payload length is 184 With that information, we should be able to do the following: >>> p = None # Outside the "with" scope... >>> with TSFile(test_ts_file) as f: ... count = 0 ... p = f.read() ... while p.is_padding(): ... count += 1 ... p = f.read() ... print count 71 >>> print p.pid 50 The PID is calculated when a TS packet is created, but otherwise the adaptation array and/or payload (and the PUSI flag) are split out lazily, as needed: >>> print p.pusi 1 >>> print p.adapt None Whether there is a PCR or not is also calculated lazily: >>> print p.PCR None The payload itself won't print out nicely, as it's binary, but we can check its length: >>> print len(p.payload) 184 and the short representation of the TS packet: >>> print p TS packet PID 0032 [pusi] P 14 00 00 01 bd 06 12 84... >>> text = ' '.join(['%02x'%x for x in p.data]) >>> print text 47 40 32 14 00 00 01 bd 06 12 84 80 0f 21 0c f5 8d 6b ff ff ff ff ff ff ff ff ff ff 0b 77 0d 73 1c 20 43 fe 21 06 a2 b8 60 75 dd 6f 87 ae 95 2c ee dc cf ae 95 5b b2 31 13 8c 56 fd 32 a5 4f a1 43 5e 95 f3 ea 6f 9e d7 75 0a 93 f5 4b 9f 4e 73 0d f2 a7 b4 df 53 54 82 1b e7 68 1f d2 7e 99 2a 97 ee 9e bf 7c a9 25 78 62 4e 7f 4a 2b a4 b4 dc d8 56 9d 31 18 4f a9 bf 7c a6 d5 c8 6a 95 3e 54 be 15 34 ca 61 42 7d 0d f2 a7 af 6c 3f 7c 43 5b e7 d5 ab b6 74 e5 fa 57 ce 9d a4 af 49 53 f4 a9 60 25 9c 95 fa 97 b7 12 99 6f 55 22 40 77 17 b2 e9 54 24 ad 59 e4 5d 2f 61 c4 36 ee >>> print repr(p) TSPacket("\x47\x40\x32\x14\x00\x00\x01\xbd\x06\x12\x84\x80\x0f\x21\x0c\xf5\x8d\x6b\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x0b\x77\x0d\x73\x1c\x20\x43\xfe\x21\x06\xa2\xb8\x60\x75\xdd\x6f\x87\xae\x95\x2c\xee\xdc\xcf\xae\x95\x5b\xb2\x31\x13\x8c\x56\xfd\x32\xa5\x4f\xa1\x43\x5e\x95\xf3\xea\x6f\x9e\xd7\x75\x0a\x93\xf5\x4b\x9f\x4e\x73\x0d\xf2\xa7\xb4\xdf\x53\x54\x82\x1b\xe7\x68\x1f\xd2\x7e\x99\x2a\x97\xee\x9e\xbf\x7c\xa9\x25\x78\x62\x4e\x7f\x4a\x2b\xa4\xb4\xdc\xd8\x56\x9d\x31\x18\x4f\xa9\xbf\x7c\xa6\xd5\xc8\x6a\x95\x3e\x54\xbe\x15\x34\xca\x61\x42\x7d\x0d\xf2\xa7\xaf\x6c\x3f\x7c\x43\x5b\xe7\xd5\xab\xb6\x74\xe5\xfa\x57\xce\x9d\xa4\xaf\x49\x53\xf4\xa9\x60\x25\x9c\x95\xfa\x97\xb7\x12\x99\x6f\x55\x22\x40\x77\x17\xb2\xe9\x54\x24\xad\x59\xe4\x5d\x2f\x61\xc4\x36\xee") Let's remember that packet: >>> first_interesting_TS_packet = p We have comparisons (but only for equality) on TS packets. Remember those first N packets are all padding: >>> f = TSFile(test_ts_file) >>> p0 = f.read() >>> p1 = f.read() >>> f.close() >>> p0 == p1 1 >>> p0 != first_interesting_TS_packet 1 >>> p0 == first_interesting_TS_packet 0 Normal iteration works as well: >>> with TSFile(test_ts_file) as f: ... count = 0 ... for packet in f: ... count += 1 ... if not packet.is_padding(): ... break ... print 'There are %d padding TS packets at the start'%count ... print 'The first non-padding packet is:',packet There are 72 padding TS packets at the start The first non-padding packet is: TS packet PID 0032 [pusi] P 14 00 00 01 bd 06 12 84... And we should be able to create our own: >>> from tstools import TSPacket >>> tscopy = TSPacket(p0.data.tostring()) >>> tscopy == p0 1 >>> x = TSPacket('fred') Traceback (most recent call last): ... TSToolsException: First byte of TS packet is 0x66, not 0x47 (XXX That ``p0.data.tostring()`` is unforgivably clumsy -- I really should make that work more naturally.) We should be able to seek, although only multiples of 188 are likely to be useful (the read after the seek to offset 27 will fail because it doesn't find a 0x47 at the start of the data it is asked to read): >>> f = TSFile(test_ts_file) >>> f.seek(71*188) >>> ps = f.read() >>> ps == first_interesting_TS_packet 1 >>> f.seek(27) >>> f.read() Traceback (most recent call last): ... TSToolsException: Error getting next TS packet from file ../data/ed24p_11.ts (First byte of TS packet is 0xff, not 0x47) >>> f.close() Note that the value returned as out data is actually an ``array`` of unsigned bytes: >>> p.data[:15] array('B', [71, 64, 50, 20, 0, 0, 1, 189, 6, 18, 132, 128, 15, 33, 12]) Python 2.6 introduces the ``bytearray`` (an immutable array of bytes), which is clearly what we'd prefer to be using to communicate with TS packets, instead of strings and ``array.array``. The first packet with a PCR is at 100768: >>> f = TSFile(test_ts_file) >>> f.seek(100768) >>> tspcr = f.read() >>> tspcr.pusi 0 >>> len(tspcr.adapt) 183 >>> tspcr.PCR 16303619382L >>> f.close() Program data ------------ A PAT is a wrapper around a lightly hidden dictionary of program number versus PMT PID. So: >>> from tstools import PAT >>> pat = PAT() >>> len(pat) 0 >>> pat[0] = 0x68 >>> pat[1] = 0x69 >>> pat[1] = 0xFFFF Traceback (most recent call last): ... ValueError: PID must be 0..0x1fff, not 0xffff >>> pat[-1] = 0x69 Traceback (most recent call last): ... ValueError: Program number must be 0..65535, not -1 >>> pat[1] 105 >>> items = [item for item in pat] >>> items.sort() >>> print items [(0, 104), (1, 105)] We should be able to retrieve (the next) PAT from our file: >>> f = TSFile(test_ts_file) >>> num_read,fp1 = f.find_PAT() >>> num_read 440 >>> print fp1 PAT({1:0x20}) For convenience, once we're found a PAT, it is remembered on the file (all sorts of caveats immediately spring to mind, since a file can contain more than one PAT - for the moment, the last PAT "found" by such a method call will be remembered). >>> print f.PAT PAT({1:0x20}) >>> f.close() Indeed, it will automatically be read in for us as we read its record(s): >>> f = TSFile(test_ts_file) >>> print f.PAT None >>> for ii in range(440): ... p = f.read() >>> print f.PAT PAT({1:0x20}) >>> f.close() We can also find PMTs in a similar manner: >>> f = TSFile(test_ts_file) >>> num_read,pmt = f.find_PMT(0x20,1) >>> num_read 441 >>> print pmt PMT program 1, version 0, PCR PID 0030 (48) >>> pmt.report() PMT program 1, version 0, PCR PID 0030 (48) Program streams: PID 0031 ( 49) -> Stream type 02 ( 2) ES info '\x52\x01\x00' PID 0032 ( 50) -> Stream type 81 (129) ES info '\x52\x01\x10' >>> f.close() The "PMT" attribute of a TSFile is a dictionary of PMT objects, keyed by the program number. It starts off empty, and gets filled in as PMT records are found, so: >>> f = TSFile(test_ts_file) >>> print f.PMT {} >>> for ii in range(441): ... p = f.read() >>> print f.PMT {1L: PMT(1,0,0x30,'')} >>> print f.PMT[1] PMT program 1, version 0, PCR PID 0030 (48) >>> f.close() And it is, of course, possible to create our own PMT: >>> from tstools import PMT, ProgramStream >>> pmt = PMT(2,1,0x68) >>> pmt.set_program_info('\x23\x47') >>> stream = ProgramStream(0x47,0x17,'\x0A\x04GER\x02') >>> stream.report() PID 0017 ( 23) -> Stream type 47 ( 71) ES info '\x0a\x04\x47\x45\x52\x02' >>> pmt.add_stream(stream) >>> pmt.report() PMT program 2, version 1, PCR PID 0068 (104) Program info '\x23\x47' Program streams: PID 0017 ( 23) -> Stream type 47 ( 71) ES info '\x0a\x04\x47\x45\x52\x02' PCR buffering ------------- There are two ways of reading TS packets from a file. The original mechanism reads each TS packet relatively directly, and only TS packets that actually contain a PCR "know" their PCR. It is then up to the calling program to try to "guess" (or, perhaps, approximate) the PCRs for intermediate packets. Later versions of tstools introduced the concept of PCR buffering. In this mode, the software will always have read forwards through the file enough to have two PCRs in hand -- the previous and the next. This allows the PCR for each TS packet in between to be determined absolutely, at the cost of a read-ahead buffer, and some special tricks before the first PCR and after the last (where linear approximation is all that can really be done). NB: at time of writing, approximation at the start of the file is not done, and thus in "PCR buffering" mode, TS packets before the first PCR are skipped. This is a bug, and should be fixed in the future. In the Python wrapping, the basic TSFile class provides "unbuffered" reading. It is thus suitable for applications that are not trying to track the PCR. The BufferedTSFile class is then a subclass of TSFile, and uses the PCR buffered interface to retrieve TS packets. So: >>> from tstools import BufferedTSFile >>> bfile = BufferedTSFile(test_ts_file) >>> print bfile >>> bfile.is_readable() 1 >>> bfile.is_writable() 0 >>> bfile.close() Since this class only opens a TSFile for read, there is no 'w' mode: >>> bfile = BufferedTSFile(test_ts_file,'w') Traceback (most recent call last): ... TypeError: function takes exactly 1 argument (2 given) >>> bfile = BufferedTSFile(test_ts_file,mode='w') Traceback (most recent call last): ... TypeError: 'mode' is an invalid keyword argument for this function I'd *like* a BufferedTSFile to act very much like a TSFile in many ways, and in particular I'd like it to start reading from the start of the file. However, at the moment it starts reading with the first TS packet after the first PMT. So in particular, the start should be readable in the same manner, which we can verify by checking how far in the "first interesting packet" we found when first testing TSFile is: x >>> p = None # Outside the "with" scope... x >>> with BufferedTSFile(test_ts_file) as f: x ... count = 0 x ... p = f.read() x ... while p.is_padding(): x ... count += 1 x ... p = f.read() x ... print count x 71 x >>> p == first_interesting_TS_packet x 1 which at the moment would fail (the responses being '0' and '0' respectively), so I shan't actually make be proper tests. What I can do is check if works as I expect: >>> p1 = p2 = p3 = None >>> with TSFile(test_ts_file) as f: ... # Read until we find the first PMT ... p = f.read() ... while len(f.PMT) == 0: ... p = f.read() ... # Then read until we find the first packet with a PCR ... p1 = f.read() ... while p1.PCR == None: ... p1 = f.read() ... p2 = f.read() ... p3 = f.read() >>> print p1 TS packet PID 0030 A 20 b7 10 01 9e 9f 5a ff... >>> print p2 TS packet PID 0031 P 1a 6c 42 26 10 88 58 30... >>> print p3 TS packet PID 0031 P 1b 19 80 3b 28 06 28 21... and thus: >>> pb1 = pb2 = pb3 = None >>> with BufferedTSFile(test_ts_file) as f: ... pb1 = f.read() ... pb2 = f.read() ... pb3 = f.read() >>> print pb1 TS packet PID 0030 A 20 b7 10 01 9e 9f 5a ff... >>> print pb2 TS packet PID 0031 P 1a 6c 42 26 10 88 58 30... >>> print pb3 TS packet PID 0031 P 1b 19 80 3b 28 06 28 21... >>> # Or, explicitly >>> p1 == pb1 and p2 == pb2 and p3 == pb3 1 As I said, not perhaps what I want, but definitely what it is currently expected to do. Opening a TSFile (for read) more than once at the same time ----------------------------------------------------------- It's not particularly obvious when coding in C, but in Python there seems no reason to expect a problem in opening a TSFile more than once for reading at the same time. For instance: >>> f1 = TSFile(test_ts_file) >>> p1 = f1.read() >>> f2 = TSFile(test_ts_file) >>> p2 = f2.read() >>> p1 == p2 1 >>> p1 = f1.read() >>> p1 = f1.read() >>> p1 = f1.read() >>> p2 = f2.read() >>> p2 = f2.read() >>> p2 = f2.read() >>> p1 == p2 1 >>> f1.close() >>> f2.close() The same should be true for BufferedTSFile: >>> f1 = BufferedTSFile(test_ts_file) >>> p1 = f1.read() >>> f2 = BufferedTSFile(test_ts_file) >>> p2 = f2.read() >>> p1 == p2 1 >>> p1 = f1.read() >>> p1 = f1.read() >>> p1 = f1.read() >>> p2 = f2.read() >>> p2 = f2.read() >>> p2 = f2.read() >>> p1 == p2 1 >>> f1.close() >>> f2.close() // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set filetype=rst tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/python/tstools/000077500000000000000000000000001261471605300177205ustar00rootroot00000000000000tstools-1.13~git20151030/python/tstools/__init__.py000066400000000000000000000025561261471605300220410ustar00rootroot00000000000000"""tstools -- a package of Pyrex bindings for the tstools This is being developed on a Mac, running OS X, and also tested on my Ubuntu system at work. I do not expect it to build (as it stands) on Windows, as it is making assumptions that may not follow thereon. It is my intent to worry about Windows after it works on the platforms that I can test most easily! """ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tibs (tibs@berlios.de) # # ***** END LICENSE BLOCK ***** # The following also makes available the "sys" and "array" modules as # imported to tsools, so is probably not the best way to do it. Ho hum. from tstools import * tstools-1.13~git20151030/python/tstools/common.pxd000066400000000000000000000113271261471605300217310ustar00rootroot00000000000000"""common.pyd -- Common definitions """ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tibs (tibs@berlios.de) # # ***** END LICENSE BLOCK ***** cdef extern from "stdio.h": ctypedef struct FILE: int _fileno cdef enum: EOF = -1 cdef FILE *stdout # Associate a stream (returned) with an existing file descriptor. # The specified mode must be compatible with the existing mode of # the file descriptor. Closing the stream will close the descriptor # as well. cdef FILE *fdopen(int fildes, char *mode) cdef FILE *fopen(char *path, char *mode) cdef int fclose(FILE *stream) cdef int fileno(FILE *stream) cdef extern from "errno.h": cdef int errno cdef extern from "string.h": cdef char *strerror(int errnum) cdef extern from "stdlib.h": cdef void free(void *ptr) # From the Cython FAQ, but according to a useful message on the Pyrex mailing # list, also applicable to Pyrex cdef extern from *: ctypedef void* const_void_ptr "const void*" ctypedef char* const_char_ptr "const char*" # Copied from the Pyrex documentation... cdef extern from "Python.h": # Return a new string object with a copy of the string v as value and # length len on success, and NULL on failure. If v is NULL, the contents of # the string are uninitialized. object PyString_FromStringAndSize(char *v, int len) # Return a NUL-terminated representation of the contents of the object obj # through the output variables buffer and length. # # The function accepts both string and Unicode objects as input. For # Unicode objects it returns the default encoded version of the object. If # length is NULL, the resulting buffer may not contain NUL characters; if # it does, the function returns -1 and a TypeError is raised. # # The buffer refers to an internal string buffer of obj, not a copy. The # data must not be modified in any way, unless the string was just created # using PyString_FromStringAndSize(NULL, size). It must not be deallocated. # If string is a Unicode object, this function computes the default # encoding of string and operates on that. If string is not a string object # at all, PyString_AsStringAndSize() returns -1 and raises TypeError. int PyString_AsStringAndSize(object obj, char **buffer, Py_ssize_t* length) except -1 # Returns a pointer to a read-only memory location containing arbitrary # data. The obj argument must support the single-segment readable buffer # interface. On success, returns 0, sets buffer to the memory location and # buffer_len to the buffer length. Returns -1 and sets a TypeError on # error. int PyObject_AsReadBuffer(object obj, const_void_ptr *buffer, Py_ssize_t *buffer_len) except -1 # Unfortunately, there are two common ways of implementing a va_list, # and we just have to guess which is being used. For the moment, though, # just take advantage of the fact that the following seems to work for # our purposes... ctypedef void * va_list cdef extern from "Python.h": FILE *PySys_GetFile(char *name, FILE *default) cdef extern from "stdint.h": ctypedef unsigned char uint8_t ctypedef unsigned uint16_t ctypedef unsigned long uint32_t ctypedef unsigned long long uint64_t ctypedef signed char int8_t ctypedef int int16_t ctypedef long int32_t ctypedef long long int64_t # PIDs are too long for 16 bits, short enough to fit in 32 ctypedef uint32_t PID cdef extern from "compat.h": # We don't need to define 'offset_t' exactly, just to let Pyrex # know it's vaguely int-like ctypedef int offset_t ctypedef uint8_t byte # but we already had our stdint byte daatype # ---------------------------------------------------------------------- # vim: set filetype=python expandtab shiftwidth=4: # [X]Emacs local variables declaration - place us into python mode # Local Variables: # mode:python # py-indent-offset:4 # End: tstools-1.13~git20151030/python/tstools/cwrapper.pxd000066400000000000000000000237041261471605300222660ustar00rootroot00000000000000"""cwrapper.pxd -- All of the C API for the tstools C library. This is being developed on a Mac, running OS X, and also tested on my Ubuntu system at work. I do not expect it to build (as it stands) on Windows, as it is making assumptions that may not follow thereon. It is my intent to worry about Windows after it works on the platforms that I can test most easily! """ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tibs (tibs@berlios.de) # # ***** END LICENSE BLOCK ***** from common cimport FILE from common cimport const_void_ptr, const_char_ptr from common cimport uint8_t, uint16_t, uint32_t, uint64_t from common cimport int8_t, int16_t, int32_t, int64_t from common cimport offset_t, byte, PID from common cimport va_list cdef extern from 'es_defns.h': # The reader for an ES file struct elementary_stream: pass ctypedef elementary_stream ES ctypedef elementary_stream *ES_p # A location within said stream struct _ES_offset: offset_t infile # as used by lseek int inpacket # in PES file, offset within PES packet ctypedef _ES_offset ES_offset # An actual ES unit struct ES_unit: ES_offset start_posn byte *data unsigned data_len unsigned data_size byte start_code byte PES_had_PTS ctypedef ES_unit *ES_unit_p cdef extern from 'es_fns.h': int open_elementary_stream(char *filename, ES_p *es) void close_elementary_stream(ES_p *es) int build_elementary_stream_file(int input, ES_p *es) void free_elementary_stream(ES_p *es) int find_and_build_next_ES_unit(ES_p es, ES_unit_p *unit) void free_ES_unit(ES_unit_p *unit) void report_ES_unit(FILE *stream, ES_unit_p unit) # We perhaps need a Python object to represent an ES_offset? # Otherwise, it's going to be hard to use them within Python itself int seek_ES(ES_p es, ES_offset where) int compare_ES_offsets(ES_offset offset1, ES_offset offset2) # I'd like to be able to *write* ES files, so... # Python file objects can return a file descriptor (i.e., integer) # via their fileno() method, so the simplest thing to do may be to # add a new C function that uses write() instead of fwrite(). Or I # could use fdopen to turn the fileno() into a FILE *... int build_ES_unit_from_data(ES_unit_p *unit, byte *data, unsigned data_len) int write_ES_unit(FILE *output, ES_unit_p unit) cdef extern from "ts_defns.h": struct _ts_reader: pass ctypedef _ts_reader TS_reader ctypedef _ts_reader *TS_reader_p cdef extern from "pidint_defns.h": struct _pidint_list: int *number uint32_t *pid int length int size ctypedef _pidint_list pidint_list ctypedef _pidint_list *pidint_list_p struct _pmt_stream: byte stream_type uint32_t elementary_PID uint16_t ES_info_length byte *ES_info ctypedef _pmt_stream pmt_stream ctypedef _pmt_stream *pmt_stream_p struct _pmt: uint16_t program_number byte version_number uint32_t PCR_pid uint16_t program_info_length byte *program_info int num_streams pmt_stream *streams ctypedef _pmt pmt ctypedef _pmt *pmt_p cdef extern from "pidint_fns.h": void free_pidint_list(pidint_list_p *list) void free_pmt(pmt_p *pmt) void report_pidint_list(pidint_list_p list, char *list_name, char *int_name, int pid_first) cdef extern from "ts_fns.h": int open_file_for_TS_read(char *filename, TS_reader_p *tsreader) int close_TS_reader(TS_reader_p *tsreader) int seek_using_TS_reader(TS_reader_p tsreader, offset_t posn) int prime_read_buffered_TS_packet(TS_reader_p tsreader, uint32_t pcr_pid) int read_next_TS_packet(TS_reader_p tsreader, byte **packet) int read_first_TS_packet_from_buffer(TS_reader_p tsreader, uint32_t pcr_pid, uint32_t start_count, byte **packet, uint32_t *pid, uint64_t *pcr, uint32_t *count) int read_next_TS_packet_from_buffer(TS_reader_p tsreader, byte **packet, uint32_t *pid, uint64_t *pcr) int split_TS_packet(byte *buf, PID *pid, int *payload_unit_start_indicator, byte **adapt, int *adapt_len, byte **payload, int *payload_len) void get_PCR_from_adaptation_field(byte *adapt, int adapt_len, int*got_pcr, uint64_t *pcr) int build_psi_data(int verbose, byte *payload, int payload_len, PID pid, byte **data, int *data_len, int *data_used) int find_pat(TS_reader_p tsreader, int max, int verbose, int quiet, int *num_read, pidint_list_p *prog_list) int find_next_pmt(TS_reader_p tsreader, uint32_t pmt_pid, int program_number, int max, int verbose, int quiet, int *num_read, pmt_p *pmt) int find_pmt(TS_reader_p tsreader, int max, int verbose, int quiet, int *num_read, pmt_p *pmt) int extract_prog_list_from_pat(int verbose, byte *data, int data_len, pidint_list_p *prog_list) int extract_pmt(int verbose, byte *data, int data_len, uint32_t pid, pmt_p *pmt) int print_descriptors(FILE *stream, char *leader1, char *leader2, byte *desc_data, int desc_data_len) cdef extern from 'nalunit_defns.h': struct nal_unit_context: pass ctypedef nal_unit_context *nal_unit_context_p struct nal_unit: pass ctypedef nal_unit *nal_unit_p struct nal_unit_list: nal_unit_p *array int length int size ctypedef nal_unit_list *nal_unit_list_p cdef extern from 'accessunit_defns.h': struct access_unit_context: pass ctypedef access_unit_context *access_unit_context_p struct access_unit: uint32_t index # Primary picture? int started_primary_picture nal_unit_p primary_start # within nal_units # Contents nal_unit_list_p nal_units cdef extern from 'nalunit_fns.h': int build_nal_unit_context(ES_p es, nal_unit_context_p *context) void free_nal_unit_context(nal_unit_context_p *context) int rewind_nal_unit_context(nal_unit_context_p context) void free_nal_unit(nal_unit_p *nal) int find_next_NAL_unit(nal_unit_context_p context, int verbose, nal_unit_p *nal) int nal_is_slice(nal_unit_p nal) int nal_is_pic_param_set(nal_unit_p nal) int nal_is_seq_param_set(nal_unit_p nal) int nal_is_redundant(nal_unit_p nal) int nal_is_first_VCL_NAL(nal_unit_p nal, nal_unit_p last) int build_nal_unit_list(nal_unit_list_p *list) int append_to_nal_unit_list(nal_unit_list_p list, nal_unit_p nal) void reset_nal_unit_list(nal_unit_list_p list, int deep) void free_nal_unit_list(nal_unit_list_p *list, int deep) cdef extern from 'accessunit_fns.h': int build_access_unit_context(ES_p es, access_unit_context_p *context) void free_access_unit_context(access_unit_context_p *context) int rewind_access_unit_context(access_unit_context_p context) void free_access_unit(access_unit_context_p *acc_unit) int get_access_unit_bounds(access_unit_context_p access_unit, ES_offset *start, uint32_t *length) int all_slices_I(access_unit_context_p access_unit) int all_slices_P(access_unit_context_p access_unit) int all_slices_I_or_P(access_unit_context_p access_unit) int all_slices_B(access_unit_context_p access_unit) int get_next_access_unit(access_unit_context_p context, int quiet, int show_details, access_unit_context_p *ret_access_unit) int get_next_h264_frame(access_unit_context_p context, int quiet, int show_details, access_unit_context_p *frame) int access_unit_has_PTS(access_unit_context_p access_unit) cdef extern from 'printing_fns.h': void print_msg(const_char_ptr text) void print_err(const_char_ptr text) void fprint_msg(const_char_ptr format, ...) void fprint_err(const_char_ptr format, ...) int redirect_output( void (*new_print_message_fn) (const_char_ptr message), void (*new_print_error_fn) (const_char_ptr message), void (*new_fprint_message_fn) (const_char_ptr format, va_list arg_ptr), void (*new_fprint_error_fn) (const_char_ptr format, va_list arg_ptr), void (*new_flush_msg_fn) () ) # This one isn't properly declared - it's not in the header files cdef extern from *: void test_C_printing() # ---------------------------------------------------------------------- # vim: set filetype=python expandtab shiftwidth=4: # [X]Emacs local variables declaration - place us into python mode # Local Variables: # mode:python # py-indent-offset:4 # End: tstools-1.13~git20151030/python/tstools/tstools.pyx000066400000000000000000001605341261471605300222020ustar00rootroot00000000000000"""tstools.pyx -- Pyrex bindings for the tstools library This is being developed on a Mac, running OS X, and also tested on my Ubuntu system at work. I do not expect it to build (as it stands) on Windows, as it is making assumptions that may not follow thereon. It is my intent to worry about Windows after it works on the platforms that I can test most easily! """ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tibs (tibs@berlios.de) # # ***** END LICENSE BLOCK ***** """ On static libraries versus dynamic libraries ============================================ Up in the main C source directories, tstools builds a static library, libtstools.a, and the tstools applications are statically linked against that. This simplifies life in many ways, but particularly: 1. Building shared/dynamic libraries in a portable manner is, well, hard (Mac OS X is particularly different). 2. Using applications built agains shared libraries means either putting the libraries in "known" locations, or setting particular paths on which to look for them. Neither of these is nice for the user to have to worrry about. Unfortunately, this complicates matters a little when wrapping the aforesaid static library with Pyrex/Cython. In an ideal world, I'd have a separate Python module (.pyx file) for each "chunk" of tstools functionality (es, ts, pes, etc.). However, if I link each of those against the static library, each gets its own copy of said library. Again, this might not be *too* much problem (space issues aside), but it totally fails if there is static data being used withing the library -- each Python module would get its own copy. See printing.c for why this is not a good thing... So, this means that tstools.pyx remains a monolithic wrapper for the whole of libtstools.a. I still think Pyrex/Cython is a better way to go than the other choices, but perhaps not as elegant as I'd wish. """ import sys import array from common cimport FILE, EOF, stdout, fopen, fclose, fileno from common cimport errno, strerror, free from common cimport const_void_ptr from common cimport PyString_FromStringAndSize, PyString_AsStringAndSize, \ PyObject_AsReadBuffer from common cimport uint8_t, uint16_t, uint32_t, uint64_t from common cimport int8_t, int16_t, int32_t, int64_t from common cimport offset_t, byte, PID cimport cwrapper from cwrapper cimport ES, ES_p, ES_offset, ES_unit, ES_unit_p from cwrapper cimport TS_reader, TS_reader_p, pidint_list, pidint_list_p from cwrapper cimport pmt_stream, pmt_stream_p, pmt, pmt_p # Is this the best thing to do? class TSToolsException(Exception): pass # ============================================================================= # Printing redirection # ============================================================================= from common cimport const_char_ptr, va_list cdef extern from "Python.h": # Write the output string described by format to sys.stdout. No exceptions # are raised, even if truncation occurs (see below). # # format should limit the total size of the formatted output string to 1000 # bytes or less – after 1000 bytes, the output string is truncated. In # particular, this means that no unrestricted “%s” formats should occur; # these should be limited using “%.s” where is a decimal number # calculated so that plus the maximum size of other formatted text does # not exceed 1000 bytes. Also watch out for “%f”, which can print hundreds # of digits for very large numbers. # # If a problem occurs, or sys.stdout is unset, the formatted message is # written to the real (C level) stdout. void PySys_WriteStdout(const_char_ptr format, ...) # Output not more than size bytes to str according to the format string # format and the variable argument list va. Unix man page vsnprintf(2). int PyOS_vsnprintf(char *str, int size, const_char_ptr format, va_list va) cdef void our_print_msg(const_char_ptr text): PySys_WriteStdout('%s',text) cdef void our_format_msg(const_char_ptr format, va_list arg_ptr): cdef int err cdef char buffer[1000] PyOS_vsnprintf(buffer, 1000, format, arg_ptr) PySys_WriteStdout('%s',buffer) cdef void our_flush(): pass def setup_printing(): cdef int err err = cwrapper.redirect_output(our_print_msg, our_print_msg, our_format_msg, our_format_msg, our_flush) if err: raise TSToolsException, 'Setting output redirection FAILED' cdef void our_doctest_print_msg(const_char_ptr text): print 'YY ' + text, cdef void our_doctest_format_msg(const_char_ptr format, va_list arg_ptr): cdef int err cdef char buffer[1000] PyOS_vsnprintf(buffer, 1000, format, arg_ptr) print 'XX ' + buffer, def setup_printing_for_doctest(): cdef int err err = cwrapper.redirect_output(our_doctest_print_msg, our_doctest_print_msg, our_doctest_format_msg, our_doctest_format_msg, our_flush) if err: raise TSToolsException, 'Setting doctest output redirection FAILED' else: print 'Printing redirected for doctest' def test_printing(): cwrapper.print_msg('Message\n') cwrapper.print_err('Error\n') #cwrapper.fprint_msg('Message "%s"\n','Fred') #cwrapper.fprint_err('Error "%s"\n','Fred') def test_c_printing(): cwrapper.test_C_printing() # ============================================================================= # ES matters # ============================================================================= cdef _hexify_C_byte_array(byte *bytes, int bytes_len): """Return a representation of a (byte) array as a hex values string. Doesn't leave any spaces between hex bytes. """ words = [] for 0 <= ii < bytes_len: words.append('\\x%02x'%bytes[ii]) return ''.join(words) cdef class ESOffset: """An offset within an ES file. If the ES unit was read directly from a raw ES file, then a simple file offset is sufficient. However, if we're reading from a PS or TS file (via the PES reading layer), then we have the offset of the PES packet, and then the offset of the ES unit therein. We *could* just use a tuple for this, but it's nice to have a bit more documentation self-evident. """ # Keep the original names, even though they're not very Pythonic cdef readonly long long infile # Hoping this is 64 bit... cdef readonly int inpacket def __cinit__(self, infile=0, inpacket=0): self.infile = infile self.inpacket = inpacket def __init__(self, infile=0, inpacket=0): pass def __str__(self): """Return a fairly compact and (relatively) self-explanatory format """ return '%d+%d'%(self.infile,self.inpacket) def __repr__(self): """Return something we could be recreated from. """ return 'ESOffset(infile=%d,inpacket=%d)'%(self.infile,self.inpacket) def formatted(self): """Return a representation that is similar to that returned by the C tools. Beware that this is +, which is reversed from the ``repr``. """ return '%08d/%08d'%(self.inpacket,self.infile) def report(self): print 'Offset %d in packet at offset %d in file'%(self.inpacket,self.infile) def __cmp__(self,other): if self.infile > other.infile: return 1 elif self.infile < other.infile: return -1 elif self.inpacket > other.inpacket: return 1 elif self.inpacket < other.inpacket: return -1 else: return 0 cdef same_ES_unit(ES_unit_p this, ES_unit_p that): """Two ES units do not need to be at the same place to be the same. """ if this.data_len != that.data_len: return False for 0 <= ii < this.data_len: if this.data[ii] != that.data[ii]: return False return True cdef class ESUnit # Forward declaration cdef object compare_ESUnits(ESUnit this, ESUnit that, int op): """op is 2 for ==, 3 for !=, other values not allowed. """ if op == 2: # == return same_ES_unit(this.unit, that.unit) elif op == 3: # != return not same_ES_unit(this.unit, that.unit) else: #return NotImplementedError raise TypeError, 'ESUnit only supports == and != comparisons' cdef class ESUnit: """A Python class representing an ES unit. """ # XXX Or would I be better of with an array.array (or, eventually, bytearray)? cdef ES_unit_p unit # It appears to be recommended to make __cinit__ expand to take more # arguments (if __init__ ever gains them), since both get the same # things passed to them. Hmm, normally I'd trust myself, but let's # try the recommended route def __cinit__(self, data=None, *args,**kwargs): cdef char *buffer cdef Py_ssize_t length if data: PyString_AsStringAndSize(data, &buffer, &length) retval = cwrapper.build_ES_unit_from_data(&self.unit, buffer, length); if retval < 0: raise TSToolsException,'Error building ES unit from Python string' def __init__(self,data=None): pass def report(self): """Report (briefly) on an ES unit. This write to C stdout, which means that Python has no control over the output. A proper Python version of this will be provided eventually. """ cwrapper.report_ES_unit(stdout, self.unit) def __dealloc__(self): cwrapper.free_ES_unit(&self.unit) def __str__(self): text = 'ES unit: start code %02x, len %4d:'%(self.unit.start_code, self.unit.data_len) for 0 <= ii < min(self.unit.data_len,8): text += ' %02x'%self.unit.data[ii] if self.unit.data_len == 9: text += ' %02x'%self.unit.data[8] elif self.unit.data_len > 9: text += '...' return text def __repr__(self): return 'ESUnit("%s")'%_hexify_C_byte_array(self.unit.data,self.unit.data_len) cdef __set_es_unit(self, ES_unit_p unit): if self.unit == NULL: raise ValueError,'ES unit already defined' else: self.unit = unit def __richcmp__(self,other,op): return compare_ESUnits(self,other,op) def __getattr__(self,name): if name == 'start_posn': return ESOffset(self.unit.start_posn.infile, self.unit.start_posn.inpacket) elif name == 'data': # Cast the first parameter so that the C compiler is happy # when compiling the (derived) tstools.c return PyString_FromStringAndSize(self.unit.data, self.unit.data_len) elif name == 'start_code': return self.unit.start_code elif name == 'PES_had_PTS': return self.unit.PES_had_PTS else: raise AttributeError cdef class ESFile: """A Python class representing an ES stream. We support opening for read, or opening (creating) a new file for write. For the moment, we don't support appending, and support for trying to read and write the same file is undefined. So, create a new ESFile as either: * ESFile(filename,'r') or * ESFile(filename,'w') Note that there is always an implicit 'b' attached to the mode (i.e., the file is accessed in binary mode). """ cdef FILE *file_stream # The corresponding C file stream cdef int fileno # and file number cdef ES_p stream # For reading an existing ES stream cdef readonly object name cdef readonly object mode # It appears to be recommended to make __cinit__ expand to take more # arguments (if __init__ ever gains them), since both get the same # things passed to them. Hmm, normally I'd trust myself, but let's # try the recommended route def __cinit__(self,filename,mode='r',*args,**kwargs): self.file_stream = fopen(filename,mode) if self.file_stream == NULL: raise TSToolsException,"Error opening file '%s'"\ " with (actual) mode '%s': %s"%(filename,mode,strerror(errno)) self.fileno = fileno(self.file_stream) if mode == 'r': retval = cwrapper.build_elementary_stream_file(self.fileno,&self.stream) if retval != 0: raise TSToolsException,'Error attaching elementary stream to file %s'%filename def __init__(self,filename,mode='r'): # What should go in __init__ and what in __cinit__ ??? self.name = filename self.mode = mode def __dealloc__(self): if self.file_stream != NULL: retval = fclose(self.file_stream) if retval != 0: raise TSToolsException,"Error closing file '%s':"\ " %s"%(self.name,strerror(errno)) if self.stream != NULL: cwrapper.free_elementary_stream(&self.stream) def __iter__(self): return self def __repr__(self): if self.name: if self.is_readable: return ""%self.name else: return ""%self.name else: return "" def is_readable(self): """This is a convenience method, whilst reading and writing are exclusive. """ return self.mode == 'r' and self.stream != NULL def is_writable(self): """This is a convenience method, whilst reading and writing are exclusive. """ return self.mode == 'w' and self.file_stream != NULL cdef _next_ESUnit(self): cdef ES_unit_p unit # The C function assumes it has a valid ES stream passed to it # = I don't think we're always called with such if self.stream == NULL: raise TSToolsException,'No ES stream to read' retval = cwrapper.find_and_build_next_ES_unit(self.stream, &unit) if retval == EOF: raise StopIteration elif retval != 0: raise TSToolsException,'Error getting next ES unit from file %s'%self.name # From http://www.philhassey.com/blog/2007/12/05/pyrex-from-confusion-to-enlightenment/ # Pyrex doesn't do type inference, so it doesn't detect that 'u' is allowed # to hold an ES_unit_p. It's up to us to *tell* it, specifically, what type # 'u' is going to be. cdef ESUnit u u = ESUnit() u.unit = unit return u # For Pyrex classes, we define a __next__ instead of a next method # in order to form our iterator def __next__(self): """Our iterator interface retrieves the ES units from the stream. """ return self._next_ESUnit() def seek(self,*args): """Seek to the given 'offset', which should be the start of an ES unit. 'offset' may be a single integer (if the file is a raw ES file), an ESOffset (for any sort of ES file), or a tuple of (infile,inpacket) Returns an ESOffset according to where it sought to. """ cdef ES_offset where try: if len(args) == 1: try: where.infile = args[0].infile where.inpacket = args[0].inpacket except: where.infile = args[0] where.inpacket = 0 elif len(args) == 2: where.infile, where.inpacket = args else: raise TypeError except: raise TypeError,'Seek argument must be one integer, two integers or an ESOffset' retval = cwrapper.seek_ES(self.stream,where) if retval != 0: raise TSToolsException,"Error seeking to %s in file '%s'"%(args,self.name) else: return ESOffset(where.infile,where.inpacket) def read(self): """Read the next ES unit from this stream. """ try: return self._next_ESUnit() except StopIteration: raise EOFError def write(self, ESUnit unit): """Write an ES unit to this stream. """ if self.file_stream == NULL: raise TSToolsException,'ESFile does not seem to have been opened for write' retval = cwrapper.write_ES_unit(self.file_stream,unit.unit) if retval != 0: raise TSToolsException,'Error writing ES unit to file %s'%self.name def close(self): # Apparently we can't call the __dealloc__ method itself, # but I think this is sensible to do here... if self.file_stream != NULL: retval = fclose(self.file_stream) if retval != 0: raise TSToolsException,"Error closing file '%s':"\ " %s"%(self.name,strerror(errno)) if self.stream != NULL: cwrapper.free_elementary_stream(&self.stream) # And obviously we're not available any more self.file_stream = NULL self.fileno = -1 self.name = None self.mode = None def __enter__(self): return self def __exit__(self, etype, value, tb): if tb is None: # No exception, so just finish normally self.close() else: # Exception occurred, so tidy up self.close() # And allow the exception to be re-raised return False # ============================================================================= # TS matters # ============================================================================= def _hexify_array(bytes): """Return a representation of an array of bytes as a hex values string. """ words = [] for val in bytes: words.append('\\x%02x'%val) return ''.join(words) class PAT(object): """A Program Association Table. Always has PID 0x0000. Data is: * * dictionary of {program_number : pid} where the 'pid' is the relevant PMT pid. """ def __init__(self, data=None): """Initialise the PAT, optionally with its dictionary. """ self._data = {} if data: # Let our own setattr method check the items make sense for key,value in data.items(): self[key] = value def __getitem__(self,key): return self._data[key] def __setitem__(self,key,value): if not (0 <= key <= 0xFFFF): raise ValueError,"Program number must be 0..65535, not %d"%key if not (0 <= value <= 0x1FFF): raise ValueError,"PID must be 0..0x1fff, not %#04x"%value self._data[key] = value def __delitem__(self,key): del self._data[key] def __len__(self): return len(self._data) def __contains__(self,key): return key in self._data def __eq__(self,other): return self._data == other._data def __iter__(self): return self._data.iteritems() def items(self): # Return the (program number, PMT PID) pairs from the PAT, # sorted by program number pairs = self._data.items() return sorted(pairs) def __repr__(self): """It is nicer if we make sure the dictionary appears in some sort of order. """ words = [] keys = self._data.keys() keys.sort() for key in keys: words.append('%d:%#x'%(key,self._data[key])) return 'PAT({%s})'%(','.join(words)) def has_PMT(self,pid): """Return whether a particular PID belongs to a PMT. """ return pid in self._data.values() def find_program_numbers(self,PMT_pid): """Given a PMT pid, return its program number(s), as a list. Note that technically one PID may be used in more than one program. Returns an empty list if the PID is not found """ # XXX Is it worth maintaining an extra (reversed) dictionary instead? program_numbers = [] for prog_num, pid in self._data(): if pid == PMT_pid: program_numbers.append(prog_num) return program_numbers # XXX Should this be an extension type, and enforce the datatypes it can hold? # XXX Or is that just too much bother? class ProgramStream(object): """A program stream, within a PMT. """ def __init__(self,stream_type,elementary_PID,es_info): self.stream_type = stream_type self.elementary_PID = elementary_PID # Use an array for the same reasons discussed in TSPacket self.es_info = array.array('B',es_info) def __str__(self): """Return a fairly compact and (relatively) self-explanatory format """ return "PID %04x (%4d) -> Stream type %02x (%3d) ES info '%s'"%(\ self.elementary_PID, self.stream_type, _hexify_array(self.es_info)) def __repr__(self): """Return something we could be recreated from. """ return "ProgramStream(%#02x,%#04x,'%s')"%(self.stream_type, self.elementary_PID, _hexify_array(self.es_info)) def formatted(self): """Return a representation that is similar to that returned by the C tools. ...not easy for program streams """ return self.__str__() def report(self,indent=2): print "%sPID %04x (%4d) -> Stream type %02x (%3d)"%(' '*indent, self.elementary_PID, self.elementary_PID, self.stream_type, self.stream_type) # XXX should actually output them as descriptors if self.es_info: print "%s ES info '%s'"%(' '*indent,_hexify_array(self.es_info)) # XXX Should this be an extension type, and enforce the datatypes it can hold? # XXX Or is that just too much bother? class PMT(object): """A Program Map Table. Data is: * program_number, version_number, PCR_pid * program_info (bytes, as a "string") * a dictionary of the streams in this program, as: * key: elementary_PID * value: (stream_type, ES_info) """ def __init__(self,program_number,version_number,PCR_pid): self.program_number = program_number self.version_number = version_number self.PCR_pid = PCR_pid # Use an array for the same reasons discussed in TSPacket self.program_info = array.array('B','') self.streams = [] def set_program_info(self,program_info): """Set our program_info bytes. """ self.program_info = array.array('B',program_info) def add_stream(self,stream): """Append a ProgramStream to our list of such. """ # I *think* this is justified, # but I still suspect I shall come to regret it if not isinstance(stream,ProgramStream): raise TypeError('Argument to PMT.add_stream should be a ProgramStream') self.streams.append(stream) def __str__(self): # XXX Don't see what I can do aboout the program info and streams return "PMT program %d, version %d, PCR PID %04x (%d)"%(self.program_number, self.version_number, self.PCR_pid, self.PCR_pid) def __repr__(self): # XXX Don't see what I can do aboout the program streams return "PMT(%d,%d,%#04x,'%s')"%(self.program_number, self.version_number, self.PCR_pid, _hexify_array(self.program_info)) def formatted(self): """Return a representation that is similar to that returned by the C tools. ...not easy for PMT """ return self.__str__() def report(self): print "PMT program %d, version %d, PCR PID %04x (%d)"%(self.program_number, self.version_number, self.PCR_pid, self.PCR_pid) # XXX should actually output them as descriptors if self.program_info: print " Program info '%s'"%_hexify_array(self.program_info) if self.streams: print " Program streams:" for stream in self.streams: stream.report(indent=4) DEF TS_PACKET_LEN = 188 cdef class TSPacket: """A convenient representation of a (dissected) TS packet. """ cdef readonly object data cdef readonly PID pid # The following are lazily calculated if necessary cdef byte _already_split cdef int _pusi # payload unit start indicator cdef object _adapt cdef object _payload # Ditto with looking for a PCR cdef int _checked_for_pcr cdef object _pcr # if we have one def __cinit__(self,buffer,*args,**kwargs): """The buffer *must* be 188 bytes long, by definition. """ # An array is easier to access than a string, and can be initialised # from any sensible sequence. This may not be the most efficient thing # to do, though, so later on we might want to consider ways of iterating # over TS entries in a file without needing to create TS packets... self.data = array.array('B',buffer) # We *really* believe that the first character had better be 0x47... if self.data[0] != 0x47: raise TSToolsException,\ 'First byte of TS packet is %#02x, not 0x47'%(ord(buffer[0])) # And the length is, well, defined if len(self.data) != TS_PACKET_LEN: raise TSToolsException,\ 'TS packet is %d bytes long, not %d'%(len(self.data)) # The PID is useful to know early on, and fairly easy to work out self.pid = ((ord(buffer[1]) & 0x1F) << 8) | ord(buffer[2]) def __init__(self,pid=None,pusi=None,adapt=None,payload=None,data=None): pass def __dealloc__(self): pass def is_padding(self): return self.pid == 0x1fff def __str__(self): self._split() text = 'TS packet PID %04x '%self.pid if self.pusi: text += '[pusi] ' if self.adapt and self.payload: text += 'A+P ' elif self.adapt: text += 'A ' elif self.payload: text += 'P ' data = self.data[3:11] words = [] for val in data: words.append('%02x'%val) text += ' '.join(words) + '...' return text def __repr__(self): return 'TSPacket("%s")'%_hexify_array(self.data) def __richcmp__(self,other,op): if op == 2: # == return self.data == other.data elif op == 3: # != return self.data != other.data else: #return NotImplementedError raise TypeError, 'TSPacket only supports == and != comparisons' def _split(self): """Split the packet up when requested to do so. """ cdef const_void_ptr buffer cdef Py_ssize_t length cdef PID pid cdef char *adapt_buf cdef int adapt_len cdef char *payload_buf cdef int payload_len cdef int retval PyObject_AsReadBuffer(self.data, &buffer, &length) retval = cwrapper.split_TS_packet(buffer,&pid,&self._pusi, &adapt_buf,&adapt_len, &payload_buf,&payload_len) if retval != 0: raise TSToolsException,'Error splitting TS packet data' if adapt_len == 0: self._adapt = None else: self._adapt = PyString_FromStringAndSize(adapt_buf,adapt_len) if payload_len == 0: self._payload = None else: self._payload = PyString_FromStringAndSize(payload_buf,payload_len) self._already_split = True def _determine_PCR(self): """Determine our PCR, if we have one. Assumes that self._split() has been called already. """ cdef const_void_ptr adapt_buf cdef Py_ssize_t adapt_len cdef int got_pcr cdef uint64_t pcr if self._adapt: PyObject_AsReadBuffer(self._adapt, &adapt_buf, &adapt_len) cwrapper.get_PCR_from_adaptation_field(adapt_buf, adapt_len, &got_pcr, &pcr) else: got_pcr = 0 self._checked_for_pcr = True # regardless if got_pcr: self._pcr = pcr def __getattr__(self,name): if not self._already_split: self._split() if name == 'pusi': return self._pusi elif name == 'adapt': return self._adapt elif name == 'payload': return self._payload elif name == "PCR": if not self._checked_for_pcr: self._determine_PCR() return self._pcr else: raise AttributeError cdef pat_from_prog_list(pidint_list_p prog_list): """Convert a program list into a PAT instance. """ try: pat = PAT() for 0 <= ii < prog_list.length: pat[prog_list.number[ii]] = prog_list.pid[ii] return pat finally: cwrapper.free_pidint_list(&prog_list) cdef pmt_from_pmt_p(pmt_p pmt): """Convert a C PMT structure into a PMT instance. XXX Should we remember the PMT's PID? Returns the new PMT object, or None if none """ try: this = PMT(pmt.program_number, pmt.version_number, pmt.PCR_pid) prog_info = PyString_FromStringAndSize(pmt.program_info, pmt.program_info_length) this.set_program_info(prog_info) for 0 <= ii < pmt.num_streams: es_info = PyString_FromStringAndSize(pmt.streams[ii].ES_info, pmt.streams[ii].ES_info_length) stream = ProgramStream(pmt.streams[ii].stream_type, pmt.streams[ii].elementary_PID, es_info) this.add_stream(stream) return this finally: cwrapper.free_pmt(&pmt) cdef class _PAT_accumulator: """This is just an accumulator for a single PAT's data. """ cdef byte *pat_data cdef int pat_data_len cdef int pat_data_used def __cinit__(self): pass def __init__(self): pass def __dealloc__(self): self.clear() def clear(self): """Clear our internal buffers """ if self.pat_data != NULL: free(self.pat_data) self.pat_data = NULL self.pat_data_len = self.pat_data_used = 0 def started(self): """Have we started accumulating data? """ return self.pat_data != NULL cdef accumulate(self, byte *payload_buf, int payload_len): """Add a bit more to our accumulating data. """ cdef int retval retval = cwrapper.build_psi_data(False,payload_buf,payload_len,0, &self.pat_data,&self.pat_data_len, &self.pat_data_used) return retval def finished(self): """Have we all the data we need for our PAT? """ return self.pat_data_len == self.pat_data_used cdef extract(self): """Finally extract an actual PAT from the accumulated data. """ cdef pidint_list_p prog_list cdef int retval retval = cwrapper.extract_prog_list_from_pat(False, self.pat_data,self.pat_data_len, &prog_list) if retval: raise TSToolsException,'Error extracting program list from PAT' return pat_from_prog_list(prog_list) cdef class _PMT_accumulator: """This is just an accumulator for a single PMT's data. """ cdef PID pid cdef byte *pmt_data cdef int pmt_data_len cdef int pmt_data_used def __cinit__(self, pid): self.pid = pid def __init__(self, pid): pass def __dealloc__(self): self.clear() def clear(self): """Clear our internal buffers """ if self.pmt_data != NULL: free(self.pmt_data) self.pmt_data = NULL self.pmt_data_len = self.pmt_data_used = 0 cdef accumulate(self, byte *payload_buf, int payload_len): """Add a bit more to our accumulating data. """ cdef int retval retval = cwrapper.build_psi_data(False,payload_buf,payload_len,self.pid, &self.pmt_data,&self.pmt_data_len, &self.pmt_data_used) return retval def finished(self): """Have we all the data we need for our PMT? """ return self.pmt_data_len == self.pmt_data_used cdef extract(self): """Finally extract an actual PMT from the accumulated data. """ cdef pmt_p pmt cdef int retval retval = cwrapper.extract_pmt(False, self.pmt_data, self.pmt_data_len, self.pid, &pmt) if retval: raise TSToolsException,'Error extracting PMT' return pmt_from_pmt_p(pmt) cdef class TSFile: """A Python class representing a TS file. We support opening for read, or opening (creating) a new file for write. For the moment, we don't support appending, and support for trying to read and write the same file is undefined. So, create a new TSFile as either: * TSFile(filename,'r') or * TSFile(filename,'w') Note that there is always an implicit 'b' attached to the mode (i.e., the file is accessed in binary mode). When reading, the default is to read with "PCR buffering" enabled. If "PCR buffering" is enabled, then we always read-ahead enough so that we have two PCRs in hand -- the previous and the next. This allows us to assign an exact PCR value to every TS packet. If "PCR buffering" is not enabled, then we only know PCR values for those TS packets that actually contain an explicit PCR. """ cdef TS_reader_p tsreader cdef readonly object name cdef readonly object mode cdef readonly object PAT # The latest PAT read, if any cdef readonly object PMT # A dictionary of {program number : PMT} # We have a byte buffer in which we accumulate partial PAT parts, # as we read TS packets cdef _PAT_accumulator PAT_data # We have a dictionary linking PMT PID to each individual accumulator # for PMT data cdef object PMT_data # It appears to be recommended to make __cinit__ expand to take more # arguments (if __init__ ever gains them), since both get the same # things passed to them. Hmm, normally I'd trust myself, but let's # try the recommended route def __cinit__(self,filename,*args,**kwargs): pass def __init__(self,filename,mode='r'): # In practice, we need to do the actual opening of the file here, # because we wish to subclassable by BufferedTSFile, which only # supports mode 'r' for its files. # However, as the Pyrex documentation warns that our __init__ # method *might* get called more than once, don't try to open # a file more than once... if self.tsreader: # Oh dear, we're already open if filename != self.filename or mode != self.mode: raise TSToolsException,"Attempt to reopen %s as '%s' with mode '%s'"%\ (self.__repr__,filename,mode) return self.name = filename self.mode = mode self.PMT = {} self.PAT_data = _PAT_accumulator() self.PMT_data = {} if mode == 'r': retval = cwrapper.open_file_for_TS_read(filename,&self.tsreader) if retval == 1: raise TSToolsException,"Error opening file '%s'"\ " for TS reading: %s"%(filename,strerror(errno)) elif mode == 'w': raise NotImplementedError,"TSFile mode 'w' is not yet available" else: raise TSToolsException,"Error opening file '%s'"\ " with mode '%s' (only 'r' and 'w' supported)"%(filename,mode) def _clear_pat_data(self): """Clear the buffers we use to accumulate PAT data (but not any actual PAT we have acquired). """ if self.PAT_data: self.PAT_data.clear() def _clear_pmt_data(self,pid): """Clear the buffers we use to accunulate PMT data (but not any actual PMT we have acquired). """ if pid in self.PMT_data: self.PMT_data[pid].clear() del self.PMT_data[pid] def _clear_all_pmt_data(self): """Clear the PMT accumulating buffers for all PIDs. """ for pid in self.PMT_data: self.PMT_data[pid].clear() self.PMT_data = {} # (__dealloc__ is apparently not allowed to call Python methods, # and Python methods don't seem to be allowed to call __dealloc__, # so let's have an intermediary) cdef _close_for_read(self): if self.tsreader != NULL: self._clear_pat_data() self._clear_all_pmt_data() self.PAT = None self.PMT = None retval = cwrapper.close_TS_reader(&self.tsreader) if retval != 0: raise TSToolsException,"Error closing file '%s':"\ " %s"%(self.name,strerror(errno)) def __dealloc__(self): self._close_for_read() #if self.tsreader != NULL: # retval = close_TS_reader(&self.tsreader) # if retval != 0: # raise TSToolsException,"Error closing file '%s':"\ # " %s"%(self.name,strerror(errno)) def __iter__(self): return self def __repr__(self): if self.name: if self.is_readable: return ""%self.name else: return ""%self.name else: return "" def is_readable(self): """This is a convenience method, whilst reading and writing are exclusive. """ return self.mode == 'r' and self.tsreader != NULL pass def is_writable(self): """This is a convenience method, whilst reading and writing are exclusive. """ return self.mode == 'w' #return self.mode == 'w' and self.file_stream != NULL pass cdef _check_pat_pmt(self, byte *buffer): cdef PID pid cdef int pusi cdef byte *adapt_buf cdef int adapt_len cdef byte *payload_buf cdef int payload_len cdef int retval retval = cwrapper.split_TS_packet(buffer, &pid, &pusi, &adapt_buf,&adapt_len, &payload_buf,&payload_len) if retval != 0: # We couldn't split it up - presumably a broken TS packet. # Ignore this problem, as the caller might legitimately want # to retrieve broken TS packets and inspect them, and our wish # to find (parts of) PAT packets shouldn't make that harder return if pid == 0: self._check_pat(pusi,adapt_buf,adapt_len,payload_buf,payload_len) else: self._check_pmt(pid,pusi,adapt_buf,adapt_len,payload_buf,payload_len) cdef _check_pat(self, int pusi, byte *adapt_buf, int adapt_len, byte *payload_buf, int payload_len): """Check if the current buffer represents (another) part of a PAT """ # Methodology borrowed from tsreport.c::report_ts cdef int retval cdef pidint_list_p prog_list cdef _PAT_accumulator this_pat_data if pusi: if self.PAT_data.started(): # Lose the PAT data we'd already partially accumulated # XXX should we grumble out loud at this? Probably not here, # XXX although note that the equivalent C code might self._clear_pat_data() else: if not self.PAT_data.started(): # It's not the start of a PAT, and we haven't got a PAT # to continue, so the best we can do is ignore it # XXX again, for the moment, quietly return # Otherwise, call the "accumulate bits of a PAT" function, # which does most of the heavy lifting for us retval = self.PAT_data.accumulate(payload_buf,payload_len) if retval: # For the moment, just give up self._clear_pat_data() return if self.PAT_data.finished(): # We've got it all try: self.PAT = self.PAT_data.extract() finally: self._clear_pat_data() cdef _check_pmt(self, PID pid, int pusi, byte *adapt_buf, int adapt_len, byte *payload_buf, int payload_len): """Check if the current buffer represents (another) part of a PMT """ # Methodology borrowed from tsreport.c::report_ts cdef int retval cdef _PMT_accumulator this_pmt_data cdef pmt_p pmt_ptr # We can't tell if this is a PMT until we've had a PAT, so: if self.PAT is None: return # So, are we actually a PMT? if not self.PAT.has_PMT(pid): return # Note that whilst we support a PMT PID belonging to more than # one program, we don't support interleaving of parts of such # - i.e., once a PMT with a given PID has started, we assume # that all the partial PMT records with the same PID belong # together... if pusi: if pid in self.PMT_data: # Lose the PMT data we'd already partially accumulated for # this PMT PID # XXX should we grumble out loud at this? Probably not here, # XXX although note that the equivalent C code might self._clear_pmt_data(pid) this_pmt_data = self.PMT_data[pid] = _PMT_accumulator(pid) else: if pid in self.PMT_data: this_pmt_data = self.PMT_data[pid] else: # It's not the start of a PMT, and we haven't got a PMT # to continue, so the best we can do is ignore it # XXX again, for the moment, quietly return # Otherwise, call the "accumulate bits of a PMT" function, # which does most of the heavy lifting for us retval = this_pmt_data.accumulate(payload_buf,payload_len) if retval: # For the moment, just give up self._clear_pmt_data(pid) return if this_pmt_data.finished(): # We've got it all try: # Finally, our PMT pmt = this_pmt_data.extract() # And remember it on the file as well self.PMT[pmt.program_number] = pmt finally: self._clear_pmt_data(pid) cdef TSPacket _next_TSPacket(self): """Read the next TS packet and return an equivalent TSPacket instance. ``filename`` is given for use in exception messages - it should be the name of the file we're reading from (using ``tsreader``). """ cdef byte *buffer if self.tsreader == NULL: raise TSToolsException,'No TS stream to read' retval = cwrapper.read_next_TS_packet(self.tsreader, &buffer) if retval == EOF: raise StopIteration elif retval == 1: raise TSToolsException,'Error getting next TS packet from file %s'%self.name # Remember the buffer we get handed a pointer to is transient # so we need to take a copy of it (which we might as well keep in # a Python object...) buffer_str = PyString_FromStringAndSize(buffer, TS_PACKET_LEN) try: new_packet = TSPacket(buffer_str) except TSToolsException, what: raise TSToolsException,\ 'Error getting next TS packet from file %s (%s)'%(self.name,what) # Check whether this packet updates our idea of the current PAT # or PMT # # (We call this *after* calling TSPacket, becuse if we call it first # then, for instance, TSPacket('\0xff') would cause split_TS_packet, # within _check_pat, to output errors on C stderr, followed by TSPacket # detecting the problem anyway) self._check_pat_pmt(buffer) return new_packet # For Pyrex classes, we define a __next__ instead of a next method # in order to form our iterator def __next__(self): """Our iterator interface retrieves the TS packets from the stream. """ return self._next_TSPacket() def seek(self,offset): """Seek to the given offset, which should be a multiple of 188. Note that the method does not check the value of 'offset'. Seeking causes the file to "forget" any PAT data it may have deduced from sequential reading of the file, or by explicit calls of find_PAT. """ self._clear_pat_data self.PAT = None retval = cwrapper.seek_using_TS_reader(self.tsreader,offset) if retval == 1: raise TSToolsException,'Error seeking to %d in file %s'%(offset,self.name) def read(self): """Read the next TS packet from this stream. """ try: return self._next_TSPacket() except StopIteration: raise EOFError def write(self, TSPacket tspacket): """Write a TS packet to this stream. """ pass def find_PAT(self,max=0,verbose=False,quiet=False): """Read TS packets to find the (next) PAT. If non-zero, `max` is the maximum number of TS packets to scan forwards whilst looking. If it is zero, there is no limit. If `verbose` is True, then extra information is output. If `quiet` is True, then the search will be as quiet as possible. Returns (num_read, pat), where `num_read` is how many TS packets were read (whether the PAT is found or not), and `pat` is None if no PAT was found. The new PAT is also saved as self.PAT (replacing, rather than updating, any previous self.PAT object). This method is more efficient than using repeated calls of ``read``, because it uses the underlying C function to find the next PAT. """ cdef pidint_list_p prog_list cdef int num_read if self.tsreader == NULL: raise TSToolsException,'No TS stream to read' retval = cwrapper.find_pat(self.tsreader,max,verbose,quiet,&num_read,&prog_list) if retval == EOF: # No PAT found return (num_read,None) elif retval == 1: raise TSToolsException,'Error searching for next PAT' # Don't forget to remember it on the file as well self.PAT = pat_from_prog_list(prog_list) return (num_read,self.PAT) def find_PMT(self,pmt_pid,program_number=-1,max=0,verbose=False,quiet=False): """Read TS packets to find the (next) PMT with PID `pmt_pid`. If `program_number` is 0 or more, then only a PMT with that program number will do, otherwise any PMT of the given PID will be OK. If non-zero, `max` is the maximum number of TS packets to scan forwards whilst looking. If it is zero, there is no limit. If `verbose` is True, then extra information is output. If `quiet` is True, then the search will be as quiet as possible. Returns (num_read, pmt), where `num_read` is how many TS packets were read (whether the PMT is found or not), and `pmt` is None if no appropriate PMT was found. The new PMT is also saved as self.PMT[progno] (replacing, rather than updating, any previous self.PMT[progno] object), where `progno` is the actual program number of the PMT. This method is more efficient than using repeated calls of ``read``, because it uses the underlying C function to find the next PMT. """ cdef pmt_p pmt cdef int num_read cdef unsigned actual_prog_num if self.tsreader == NULL: raise TSToolsException,'No TS stream to read' retval = cwrapper.find_next_pmt(self.tsreader,pmt_pid,program_number,max,verbose,quiet, &num_read,&pmt) if retval == EOF: # No PMT found return (num_read,None) elif retval == 1: raise TSToolsException,'Error searching for next PMT' this_pmt = pmt_from_pmt_p(pmt) # Don't forget to remember it on the file as well self.PMT[this_pmt.program_number] = this_pmt return (num_read,this_pmt) def close(self): ## Since we don't appear to be able to call our __dealloc__ "method", ## and we're not allowed to call Python methods.. #if self.tsreader != NULL: # retval = close_TS_reader(&self.tsreader) # if retval != 0: # raise TSToolsException,"Error closing file '%s':"\ # " %s"%(self.name,strerror(errno)) self._close_for_read() self.name = None self.mode = None def __enter__(self): return self def __exit__(self, etype, value, tb): if tb is None: # No exception, so just finish normally self.close() else: # Exception occurred, so tidy up self.close() # And allow the exception to be re-raised return False cdef class BufferedTSFile(TSFile): """A Python class representing a PCR-buffered TS file. This provides a read-only TSFile in which all TS packets have a reliable PCR. This is managed by: 1. Locating the first PAT. 2. Locating the first PMT associated with that PAT 3. Reading TS packets until two PMTs have been found with a PCR. 4. Deducing the PCR values for intermediate TS packets based on those PCRs and the locations of the PMT packets within the file. 5. "Rewinding" back to the first PMT to beging reading packets. Note that this last means the first packets of the file are likely to be ignored, which is a bug, and should eventually be fixed. Further note that the current implementation doesn't offer any means of changing which PMT PID is used, which program is selected, etc -- the PMT from the first program stream in the first PAT will be the one chosen. """ cdef object got_first # Have we already read the first TS packet? cdef object pcr_pid # The PID we're using for our PCRs cdef uint32_t start_count # A hack # The __cinit__ of our base type (TSFile) is automatically called # for us, before our own __cinit__ def __cinit__(self,filename,*args,**kwargs): pass def __init__(self,filename): """Open the given file for reading via the PCR buffering mechanism. """ super(BufferedTSFile,self).__init__(filename,mode='r') # Locate our first PMT (num_read,PAT) = self.find_PAT() if PAT is None: raise TSToolsException,"Unable to find PAT in file '%s'"%self.name self.start_count = num_read # Choose the first program from therein (the list returned is sorted # by program number) programs = PAT.items() if len(programs) == 0: raise TSToolsException,"No programs in first PAT in '%s'"%self.name # Find the PMT for the first program (progno,PMT_pid) = programs[0] (num_read,PMT) = self.find_PMT(PMT_pid,progno) if PMT is None: raise TSToolsException,"Unable to find PMT with PID %04x"\ " for program %d in file '%s'"%(PMT_pid,progno,self.name) self.start_count += num_read self.pcr_pid = PMT.PCR_pid # Tell the read mechanism which PCR PID we want to use retval = cwrapper.prime_read_buffered_TS_packet(self.tsreader,self.pcr_pid) if retval == 1: raise TSToolsException,'Error priming PCR read ahead for file %s'%self.name def __repr__(self): if self.name: return ""%self.name else: return "" def write(self, TSPacket tspacket): """BufferedTSFiles do not support writing. """ raise NotImplementedError,'BufferedTSFiles do not support writing' cdef TSPacket _next_TSPacket(self): """Read the next TS packet and return an equivalent TSPacket instance. ``filename`` is given for use in exception messages - it should be the name of the file we're reading from (using ``tsreader``). """ cdef byte *buffer cdef PID pid cdef uint64_t pcr cdef uint32_t count if self.tsreader == NULL: raise TSToolsException,'No TS stream to read' if self.got_first: retval = cwrapper.read_next_TS_packet_from_buffer(self.tsreader, &buffer, &pid, &pcr) else: retval = cwrapper.read_first_TS_packet_from_buffer(self.tsreader, self.pcr_pid, self.start_count, &buffer, &pid, &pcr, &count) if retval == EOF: raise StopIteration elif retval == 1: raise TSToolsException,'Error getting next TS packet from file %s'%self.name self.got_first = True # Remember the buffer we get handed a pointer to is transient # so we need to take a copy of it (which we might as well keep in # a Python object...) buffer_str = PyString_FromStringAndSize(buffer, TS_PACKET_LEN) try: # XXX And we really must tell the TSPacket that we *know* its PCR new_packet = TSPacket(buffer_str) except TSToolsException, what: raise TSToolsException,\ 'Error getting next TS packet from file %s (%s)'%(self.name,what) # Check whether this packet updates our idea of the current PAT # or PMT # # (We call this *after* calling TSPacket, becuse if we call it first # then, for instance, TSPacket('\0xff') would cause split_TS_packet, # within _check_pat, to output errors on C stderr, followed by TSPacket # detecting the problem anyway) self._check_pat_pmt(buffer) return new_packet # ---------------------------------------------------------------------- # vim: set filetype=python expandtab shiftwidth=4: # [X]Emacs local variables declaration - place us into python mode # Local Variables: # mode:python # py-indent-offset:4 # End: tstools-1.13~git20151030/reverse.c000066400000000000000000001551741261471605300165240ustar00rootroot00000000000000/* * Support for reversing * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ /* The reverse-data arrays are populated as a side-effect of processing the * file forwards. The "build a picture" routines in h262.c and accessunit.c * call the "remember" functions herein for appropriate pictures. * * Reversing is then handled by going backwards through those arrays, selecting * appropriate "rememebered" values. For H.264 pictures, and for H.262 sequence * headers, it is sufficient to read the appropriate "chunk" of memory back * in (from the ES data stream), and just output that without any further work. * * For H.262 pictures, life is a little more complex, as some of the frames * contain AFD user data, which overrides the aspect ratio in the preceding * sequence header. If one thus wants to precede each frame *with* a sequence * header (which is a Good Idea), then one also needs to ensure it has an * appropriate AFD entry (whether it did in the original data or not). * * Thus for H.262 pictures, one must re-read the picture data from the input * file (starting at the specified offset), and "invent" an AFD entry within * its item list, if necessary. */ #include #include #include #include #include // for ctime/time #include "compat.h" #include "misc_defns.h" #include "printing_fns.h" #include "es_fns.h" #include "h262_fns.h" #include "nalunit_fns.h" #include "accessunit_fns.h" #include "ts_fns.h" #include "tswrite_fns.h" #include "reverse_fns.h" #define DEBUG 0 // ------------------------------------------------------------ // A useful macro to tell us if the `idx` entry in the reverse_data // structure `rev` is a sequence header or not (or did you guess?) #define SEQUENCE_HEADER_ENTRY(rev,idx) (!(rev)->is_h264 && \ (rev)->seq_offset[idx] == 0) // ============================================================ // Remembering start/length information for reversing video sequences // ============================================================ /* * Build the internal arrays to remember video sequence bounds in, * for reversing. * * Builds a new `reverse_data` datastructure. If `is_h264` is FALSE (i.e., the * data to be reversed is not MPEG-1 or MPEG-2), then this datastructure may * be smaller. * * To collect reversing data, attach this datastructure to an H.262 or access * unit context (with add_h262/access_unit_reverse_context), and then use * get_next_h262_frame() or get_next_h264_frame() to read through the data * stream - appropriate pictures/access units will be remembered * automatically. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_reverse_data(reverse_data_p *reverse_data, int is_h264) { int newsize = REVERSE_ARRAY_START_SIZE; reverse_data_p new = malloc(SIZEOF_REVERSE_DATA); if (new == NULL) { print_err("### Unable to allocate reverse data datastructure\n"); return 1; } new->start_file = malloc(newsize*sizeof(offset_t)); if (new->start_file == NULL) { print_err("### Unable to allocate reverse data array (start_file)\n"); free(new); return 1; } new->start_pkt = malloc(newsize*sizeof(int32_t)); if (new->start_pkt == NULL) { print_err("### Unable to allocate reverse data array (start_pkt)\n"); free(new->start_file); free(new); return 1; } new->index = malloc(newsize*sizeof(uint32_t)); if (new->index == NULL) { print_err("### Unable to allocate reverse data array (index)\n"); free(new->start_file); free(new->start_pkt); free(new); return 1; } new->data_len = malloc(newsize*sizeof(int32_t)); if (new->data_len == NULL) { print_err("### Unable to allocate reverse data array (data_len)\n"); free(new->start_file); free(new->start_pkt); free(new->index); free(new); return 1; } if (is_h264) { new->seq_offset = NULL; new->afd_byte = NULL; } else { new->seq_offset = malloc(newsize); if (new->seq_offset == NULL) { print_err("### Unable to allocate reverse data array (seq offset)\n"); free(new->start_file); free(new->start_pkt); free(new->index); free(new->data_len); free(new); return 1; } new->afd_byte = malloc(newsize); if (new->afd_byte == NULL) { print_err("### Unable to allocate reverse data array (AFD)\n"); free(new->seq_offset); free(new->start_file); free(new->start_pkt); free(new->index); free(new->data_len); free(new); return 1; } } new->size = newsize; new->length = 0; new->num_pictures = 0; new->is_h264 = is_h264; new->pictures_written = 0; new->pictures_kept = 0; new->first_written = 0; new->last_written = 0; new->last_posn_added = 0; // although undefined if `length` == 0 new->output_sequence_headers = !is_h264; new->pid = DEFAULT_VIDEO_PID; new->stream_id = DEFAULT_VIDEO_STREAM_ID; *reverse_data = new; return 0; } /* * Set the video PID and stream id for TS output. * * This need only be called if reverse data *is* being output as TS, * and if the standard default values (DEFAULT_VIDEO_PID and * DEFAULT_VIDEO_STREAM_ID) are not correct. */ extern void set_reverse_pid(reverse_data_p reverse_data, uint32_t pid, byte stream_id) { reverse_data->pid = pid; reverse_data->stream_id = stream_id; } /* * Add a reversing context to an H.262 context (and vice versa). * * Does not check if there is one present already. * * Returns 0 if all is well, 1 if something goes wrong. */ extern int add_h262_reverse_context(h262_context_p h262, reverse_data_p reverse_data) { if (reverse_data->is_h264) { print_err("### Cannot add an H.262 context to an H.264 reverse data" " context\n"); return 1; } h262->reverse_data = reverse_data; reverse_data->h262 = h262; return 0; } /* * Add a reversing context to an access unit context (and vice versa). * * Does not check if there is one present already. * * Returns 0 if all is well, 1 if something goes wrong. */ extern int add_access_unit_reverse_context(access_unit_context_p context, reverse_data_p reverse_data) { if (!reverse_data->is_h264) { print_err("### Cannot add an H.264 access unit context to an" " H.262 reverse data context\n"); return 1; } context->reverse_data = reverse_data; reverse_data->h264 = context; return 0; } /* * Free the datastructure we used to remember reversing data * * Sets `reverse_data` to NULL. */ extern void free_reverse_data(reverse_data_p *reverse_data) { reverse_data_p this = *reverse_data; if (this == NULL) return; if (this->seq_offset != NULL) { free(this->seq_offset); this->seq_offset = NULL; } free(this->index); free(this->start_file); free(this->start_pkt); free(this->data_len); this->index = NULL; this->start_file = NULL; this->start_pkt = NULL; this->data_len = NULL; this->length = this->size = 0; free(this); *reverse_data = NULL; } /* * Compare an offset and two position components. `offset2` is composed * of file_posn2 and pkt_posn2. * * Returns -1 if offset1 < offset2, 0 if they are the same, and 1 if * offset1 > offset2. */ static inline int cmp_offsets(ES_offset offset1, offset_t file_posn2, int32_t pkt_posn2) { if (offset1.infile < file_posn2) return -1; else if (offset1.infile > file_posn2) return 1; else if (offset1.inpacket < pkt_posn2) return -1; else if (offset1.inpacket > pkt_posn2) return 1; else return 0; } static void debug_reverse_data_problem(reverse_data_p reverse_data, uint32_t index, ES_offset start_posn, uint32_t idx) { FILE *tempfile; char *tempfilename = "tsserve_reverse_problem.txt"; int ii; tempfile = fopen(tempfilename,"a+"); if (tempfile == NULL) { fprint_err("### Unable to open file %s - writing diagnostics" " to stderr instead\n",tempfilename); tempfile = stderr; } else { time_t now; fprint_err("### Appending diagnostics to file %s\n",tempfilename); now = time(NULL); fprintf(tempfile,"** %s:\n",ctime(&now)); } fprintf(tempfile,"Trying to add reverse data [%d] " OFFSET_T_FORMAT "/%d at index %d (again),\nbut previous entry was [%d] " OFFSET_T_FORMAT "/%d\n", index,start_posn.infile,start_posn.inpacket,idx, reverse_data->index[idx],reverse_data->start_file[idx], reverse_data->start_pkt[idx]); fprintf(tempfile,"Last posn added %d, length %d, index %d\n", reverse_data->last_posn_added,reverse_data->length,index); for (ii=0; iilength; ii++) if (reverse_data->is_h264 || reverse_data->seq_offset[ii]) fprintf(tempfile," %3d: %4d at " OFFSET_T_FORMAT "/%d for %d\n", ii,reverse_data->index[ii], reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); else fprintf(tempfile," %3d: seqh at " OFFSET_T_FORMAT "/%d for %d\n", ii, reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); if (tempfile != stderr) { fprintf(tempfile,"\n \n"); fclose(tempfile); } } /* * Remember video sequence bounds for H.262 data * * - `reverse_data` is the datastructure we want to add our entry to * - `index` indicates which picture (counted from the start of the file) * this one is (i.e., we're assuming that not all pictures will be stored). * If the entry is an H.262 sequence header, then this is ignored. * - `start_posn` is the location of the start of the entry in the file, * The entry will be ignored if `start_posn` comes before the last * existing entry in the arrays. * - `length` is the number of bytes in the entry * - `seq_offset` should be 0 for a sequence header, and is otherwise the * offset backwards to the previous nearest sequence header (i.e., 1 if * the sequence header is the previous entry). * - `afd` is the effective AFD byte for this picture * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remember_reverse_h262_data(reverse_data_p reverse_data, uint32_t index, ES_offset start_posn, uint32_t length, byte seq_offset, byte afd) { if (reverse_data->length > 0 && (reverse_data->last_posn_added + 1) < (uint32_t)reverse_data->length) { // We're repeating an entry we previously added - check it hasn't // changed (since the only obvious way for this to have happened // is if we've rewound and are then moving forwards again, it should // not be possible for the data to have changed at a particular index) int idx = reverse_data->last_posn_added + 1; int cmp = cmp_offsets(start_posn, reverse_data->start_file[idx], reverse_data->start_pkt[idx]); if (cmp == 0) { #if DEBUG fprint_msg("++ Added [%d] " OFFSET_T_FORMAT "/%d again\n", index,start_posn.infile,start_posn.inpacket); #endif reverse_data->last_posn_added ++; return 0; } else { fprint_err("### Trying to add reverse data [%d] " OFFSET_T_FORMAT "/%d at index %d (again),\n but previous entry was [%d] " OFFSET_T_FORMAT "/%d\n", index,start_posn.infile,start_posn.inpacket,idx, reverse_data->index[idx],reverse_data->start_file[idx], reverse_data->start_pkt[idx]); debug_reverse_data_problem(reverse_data,index,start_posn,idx); return 1; } } if (reverse_data->size == reverse_data->length) { int newsize = reverse_data->size + REVERSE_ARRAY_INCREMENT_SIZE; reverse_data->index = realloc(reverse_data->index, newsize*sizeof(uint32_t)); if (reverse_data->index == NULL) { print_err("### Unable to extend reverse data array (index)\n"); return 1; } reverse_data->start_file = realloc(reverse_data->start_file, newsize*sizeof(offset_t)); if (reverse_data->start_file == NULL) { print_err("### Unable to extend reverse data array (start_file)\n"); return 1; } reverse_data->start_pkt = realloc(reverse_data->start_pkt, newsize*sizeof(int32_t)); if (reverse_data->start_pkt == NULL) { print_err("### Unable to extend reverse data array (start_pkt)\n"); return 1; } reverse_data->data_len = realloc(reverse_data->data_len, newsize*sizeof(int32_t)); if (reverse_data->data_len == NULL) { print_err("### Unable to extend reverse data array (length)\n"); return 1; } if (!reverse_data->is_h264) { reverse_data->seq_offset = realloc(reverse_data->seq_offset,newsize); if (reverse_data->seq_offset == NULL) { print_err("### Unable to extend reverse data array (seq offset)\n"); return 1; } reverse_data->afd_byte = realloc(reverse_data->afd_byte,newsize); if (reverse_data->afd_byte == NULL) { print_err("### Unable to extend reverse data array (AFD)\n"); return 1; } } reverse_data->size = newsize; } // If we're not an H.262 sequence header, remember our index if (seq_offset != 0) { reverse_data->num_pictures ++; reverse_data->index[reverse_data->length] = index; reverse_data->seq_offset[reverse_data->length] = seq_offset; reverse_data->afd_byte[reverse_data->length] = afd; } else { reverse_data->index[reverse_data->length] = 0; reverse_data->seq_offset[reverse_data->length] = 0; reverse_data->afd_byte[reverse_data->length] = 0; } reverse_data->start_file[reverse_data->length] = start_posn.infile; reverse_data->start_pkt[reverse_data->length] = start_posn.inpacket; reverse_data->data_len[reverse_data->length] = length; reverse_data->last_posn_added = reverse_data->length; reverse_data->length ++; return 0; } /* * Remember video sequence bounds for H.264 data * * - `reverse_data` is the datastructure we want to add our entry to * - `index` indicates which picture (counted from the start of the file) * this one is (i.e., we're assuming that not all pictures will be stored). * If the entry is an H.262 sequence header, then this is ignored. * - `start_posn` is the location of the start of the entry in the file, * The entry will be ignored if `start_posn` comes before the last * existing entry in the arrays. * - `length` is the number of bytes in the entry * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remember_reverse_h264_data(reverse_data_p reverse_data, uint32_t index, ES_offset start_posn, uint32_t length) { if (reverse_data->length > 0 && (reverse_data->last_posn_added + 1) < (uint32_t)reverse_data->length) { // We're repeating an entry we previously added - check it hasn't // changed (since the only obvious way for this to have happened // is if we've rewound and are then moving forwards again, it should // not be possible for the data to have changed at a particular index) int idx = reverse_data->last_posn_added + 1; int cmp = cmp_offsets(start_posn, reverse_data->start_file[idx], reverse_data->start_pkt[idx]); if (cmp == 0) { #if DEBUG fprint_msg("++ Added [%d] " OFFSET_T_FORMAT "/%d again\n", index,start_posn.infile,start_posn.inpacket); #endif reverse_data->last_posn_added ++; return 0; } else { fprint_err("### Trying to add reverse data [%d] " OFFSET_T_FORMAT "/%d at index %d (again),\n but previous entry was [%d] " OFFSET_T_FORMAT "/%d\n", index,start_posn.infile,start_posn.inpacket,idx, reverse_data->index[idx],reverse_data->start_file[idx], reverse_data->start_pkt[idx]); debug_reverse_data_problem(reverse_data,index,start_posn,idx); return 1; } } if (reverse_data->size == reverse_data->length) { int newsize = reverse_data->size + REVERSE_ARRAY_INCREMENT_SIZE; reverse_data->index = realloc(reverse_data->index, newsize*sizeof(uint32_t)); if (reverse_data->index == NULL) { print_err("### Unable to extend reverse data array (index)\n"); return 1; } reverse_data->start_file = realloc(reverse_data->start_file, newsize*sizeof(offset_t)); if (reverse_data->start_file == NULL) { print_err("### Unable to extend reverse data array (start_file)\n"); return 1; } reverse_data->start_pkt = realloc(reverse_data->start_pkt, newsize*sizeof(int32_t)); if (reverse_data->start_pkt == NULL) { print_err("### Unable to extend reverse data array (start_pkt)\n"); return 1; } reverse_data->data_len = realloc(reverse_data->data_len, newsize*sizeof(int32_t)); if (reverse_data->data_len == NULL) { print_err("### Unable to extend reverse data array (length)\n"); return 1; } reverse_data->size = newsize; } reverse_data->num_pictures ++; reverse_data->index[reverse_data->length] = index; reverse_data->start_file[reverse_data->length] = start_posn.infile; reverse_data->start_pkt[reverse_data->length] = start_posn.inpacket; reverse_data->data_len[reverse_data->length] = length; reverse_data->last_posn_added = reverse_data->length; reverse_data->length ++; return 0; } /* * Retrieve video sequence bounds for entry `which` * * - `reverse_data` is the datastructure we want to get our entry from * - `which` indicates which entry we'd like to retrieve. The first * entry in the `reverse_data` is number 0. * - `index` indicates which picture (counted from the start of the file) * this one is (i.e., we're assuming that not all pictures will be stored). * `index` may be passed as NULL if the value is of no interest - i.e., * typically when the entry is for an H.262 sequence header. * The first picture in the file has index 1. * - `start_posn` is the location of the start of the entry in the file, * - `length` is the number of bytes in the entry * - for H.262 data, if the entry is a picture, then `seq_offset` will * be the offset backwards to the previous nearest sequence header * (i.e., 1 if the sequence header is the previous entry), and if it is * a sequence header, `seq_offset` will be 0. For H.264 data, the value * will always be 0. `seq_offset` may be passed as NULL if the value is * of no interest. * - for H.262 data, if the entry is a picture, then `afd` will be its * (effective) AFD byte. Otherwise it will be 0. `afd` may be passed as NULL * if the value if of no interest. * * To clarify, all of the following are legitimate calls:: * * err = get_reverse_data(reverse_data,10,&index,&start,&length,&offset,&afd); * err = get_reverse_data(reverse_data,10,&index,&start,&length,NULL,NULL); * err = get_reverse_data(reverse_data,10,NULL,&start,&length,NULL,NULL); * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int get_reverse_data(reverse_data_p reverse_data, int which, uint32_t *index, ES_offset *start_posn, uint32_t *length, byte *seq_offset, byte *afd) { if (which >= reverse_data->length || which < 0) { fprint_err("Requested reverse data index (%d) is out of range 0-%d\n", which,reverse_data->length-1); return 1; } if (index != NULL) *index = reverse_data->index[which]; start_posn->infile = reverse_data->start_file[which]; start_posn->inpacket = reverse_data->start_pkt[which]; *length = reverse_data->data_len[which]; if (seq_offset != NULL) { if (reverse_data->is_h264) *seq_offset = 0; else *seq_offset = reverse_data->seq_offset[which]; } if (afd != NULL) { if (reverse_data->is_h264) *afd = 0; else *afd = reverse_data->afd_byte[which]; } return 0; } // ============================================================ // Collecting pictures // ============================================================ /* * Locate and remember sequence headers and I pictures, for later reversal. * * - `h262` is the H.262 stream reading context * - if `max` is non-zero, then collecting will stop after `max` pictures * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if all went well, EOF if the end of file is reached, * and 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int collect_reverse_h262(h262_context_p h262, int max, int verbose, int quiet) { int err = 0; // In order to stop after `max` items, we need to count pictures int picture_count = 0; if (h262->reverse_data == NULL) { print_err("### Unable to collect reverse data for H.262 pictures\n"); print_err(" H.262 context does not have reverse data" " information attached to it\n"); return 1; } for (;;) { h262_picture_p picture = NULL; if (es_command_changed(h262->es)) return COMMAND_RETURN_CODE; err = get_next_h262_frame(h262,verbose,quiet,&picture); if (err == EOF) return EOF; else if (err) return 1; if (picture->is_picture) picture_count ++; free_h262_picture(&picture); if (max > 0 && picture_count >= max) break; } return 0; } /* * Find IDR and I slices, and remember their access units for later output * in reverse order. * * - `acontext` is the access unit reading context * - if `max` is non-zero, then collecting will stop after `max` access units * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if all went well, EOF if the end of file is reached, * and 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int collect_reverse_access_units(access_unit_context_p acontext, int max, int verbose, int quiet) { int err = 0; int access_unit_count = 0; if (acontext->reverse_data == NULL) { print_err("### Unable to collect reverse data for access units\n"); print_err(" Access unit context does not have reverse data" " information attached to it\n"); return 1; } for (;;) { access_unit_p access_unit; if (es_command_changed(acontext->nac->es)) return COMMAND_RETURN_CODE; if (verbose) print_msg("\n"); err = get_next_h264_frame(acontext,quiet,verbose,&access_unit); if (err == EOF) return EOF; else if (err) return 1; access_unit_count ++; free_access_unit(&access_unit); if (!verbose && !quiet && (access_unit_count % 5000 == 0)) fprint_msg("Scanned %d NAL units in %d frames," " remembered %d frames\n", acontext->nac->count,access_unit_count, acontext->reverse_data->length); // Did the logical stream end after the last access unit? if (acontext->end_of_stream) { if (!quiet) print_msg("Found End-of-stream NAL unit\n"); break; } if (max > 0 && access_unit_count >= max) { if (verbose) fprint_msg("\nStopping because %d frames have been read\n", access_unit_count); break; } } return 0; } /* * Write out packet data as ES or TS * * ``extern`` (but unadvertised in a header file) so that it can be used * internally in esreverse.c * * Note that the last two arguments (`pid` and `stream_id`) are only * used if the data `is_TS`. */ extern int write_packet_data(WRITER output, int as_TS, byte data[], int data_len, uint32_t pid, byte stream_id) { int err; if (as_TS) { // If we're writing TS data, then wrap the whole thing up as PES and // write it out as TS packets. // Unfortunately, it's not *quite* that simple, as a PES packet has // a length specified either as a 16 bit quantity, or as 0 (allowed // for video data). And it is known that some pictures are longer // than 65535 bytes. err = write_ES_as_TS_PES_packet(output.ts_output,data,data_len, pid,stream_id); if (err) { print_err("### Error writing data as TS PES packet\n"); return 1; } } else { // Otherwise, just write it out as is size_t written = fwrite(data,1,data_len,output.es_output); if (written != data_len) { fprint_err("### Error writing out data: %s\n" " Wrote %d bytes instead of %d\n", strerror(errno),(int)written,data_len); return 1; } } return 0; } /* * Write out H.262 picture data as ES or TS */ static int write_picture_data(WRITER output, int as_TS, h262_picture_p picture, uint32_t pid) { int err; if (as_TS) { err = write_h262_picture_as_TS(output.ts_output,picture,pid); if (err) { print_err("### Error writing data as TS PES packet\n"); return 1; } } else { err = write_h262_picture_as_ES(output.es_output,picture); if (err) { print_err("### Error writing data as ES\n"); return 1; } } return 0; } /* * Read an H.262 picture from the given location * * Note that this only does the bare minimum necessary for our purposes * here - it is *not* an adequate basis for a general "seek in H.262" mechanism * (which is why it is here, rather than in h262.c). * * Returns 0 if all goes well, 1 if something goes wrong. */ static int read_h262_picture(h262_context_p context, ES_offset where, byte afd, int verbose, h262_picture_p *picture) { int err; reverse_data_p reverse_data = NULL; // Ensure that the H.262 context doesn't think it has any ES items in hand // so that it will start building a picture from scratch if (context->last_item) free_h262_item(&context->last_item); err = seek_ES(context->es,where); if (err) { print_err("### Error seeking for H.262 picture to reverse\n"); return 1; } // Hmm - we don't want this call to "remember" the picture for us, // since we've already done so. Thus we'll have to pretend that we // don't have a reverse data context whilst we're making the call... reverse_data = context->reverse_data; context->reverse_data = NULL; // But we *do* want to insist that the picture contain an AFD context->add_fake_afd = TRUE; context->last_afd = afd; // the value to use if the picture doesn't have one err = get_next_h262_frame(context,verbose,TRUE,picture); context->reverse_data = reverse_data; if (err) { print_err("### Error reading H.262 picture when reversing\n"); return 1; } return 0; } /* * Output an H.262 sequence header. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `as_TS` is true, then output as TS packets, not ES * - if `verbose` is true, then extra information will be output * - `seq_index` is the index of the sequence header in the `reverse_data` * - `reverse_data` contains the list of pictures/access units to reverse. */ static int output_sequence_header(ES_p es, WRITER output, int as_TS, int verbose, uint16_t seq_index, reverse_data_p reverse_data) { int err; ES_offset seq_posn; uint32_t seq_len; byte *seq_data = NULL; err = get_reverse_data(reverse_data,seq_index,NULL,&seq_posn,&seq_len, NULL,NULL); if (err) { fprint_err("### Error retrieving sequence header location at %d\n", seq_index); return 1; } if (verbose) fprint_msg("Writing sequence header %2d from " OFFSET_T_FORMAT_08 "/%04d for %5d\n", seq_index,seq_posn.infile,seq_posn.inpacket,seq_len); err = read_ES_data(es,seq_posn,seq_len,NULL,&seq_data); if (err) { fprint_err("### Error reading (sequence header) data" " from " OFFSET_T_FORMAT "/%d for %d\n", seq_posn.infile,seq_posn.inpacket,seq_len); return 1; } err = write_packet_data(output,as_TS,seq_data,seq_len,reverse_data->pid, reverse_data->stream_id); free(seq_data); if (err) { print_err("### Error writing (sequence header) data as" " TS PES packet\n"); return 1; } return 0; } /* * Output the last picture (or an earlier one) from the reverse arrays. * * This is expected to be used after the whole of the data stream has been * played, so that the last picture in the reverse arrays is the last I or * IDR picture in the data stream. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `as_TS` is true, then output as TS packets, not ES * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `offset` is the offset from the end of the array of the picture * to output - so 0 means the last picture, 1 the picture before that, * and so on. Sequence headers do not count for this purpose. * - `reverse_data` is the reverse data context. * * Returns 0 if all went well, or 1 if something went wrong. */ static int output_from_reverse_data(ES_p es, WRITER output, int as_TS, int verbose, int quiet, uint32_t offset, reverse_data_p reverse_data) { int with_sequence_headers = (!reverse_data->is_h264 && reverse_data->output_sequence_headers); uint32_t which = reverse_data->length - 1; // the maximum picture index int is_h262 = !reverse_data->is_h264; int err; uint32_t index; ES_offset start_posn; uint32_t num_bytes; byte seq_offset; byte afd; uint32_t uu; if (verbose) fprint_msg("\nGOING BACK: offset %u, max pic index %u\n",offset,which); // Check we have some data to work with, so we don't try to index // nonexistant entries in the arrays if (reverse_data->length == 0) return 0; // Start with the last non-sequence header, and work backwards if (which > 0 && SEQUENCE_HEADER_ENTRY(reverse_data,which)) which --; if (verbose) fprint_msg(" last non-sequence header picture is %u\n",which); for (uu = 0; uu < offset; uu++) { if (which > 1) { which --; if (SEQUENCE_HEADER_ENTRY(reverse_data,which)) which --; } if (verbose) fprint_msg(" back %u to %d\n",uu,which); } // And let's output that picture... err = get_reverse_data(reverse_data,which,&index,&start_posn,&num_bytes, &seq_offset,&afd); if (err) return 1; if (verbose) fprint_msg("Picture [%03d] %4d from " OFFSET_T_FORMAT_08 "/%04d for %5d\n", which,index,start_posn.infile,start_posn.inpacket,num_bytes); if (with_sequence_headers) { // Make sure we've output its sequence header err = output_sequence_header(es,output,as_TS,verbose,(uint16_t)(which - seq_offset), reverse_data); if (err) { fprint_err("### Error retrieving sequence header" " for picture %d (offset %d)\n",which,seq_offset); return 1; } } if (is_h262) { h262_picture_p picture; err = read_h262_picture(reverse_data->h262,start_posn,afd,verbose, &picture); if (err) { fprint_err("### Error reading H.262 picture from " OFFSET_T_FORMAT "/%d for %d\n", start_posn.infile,start_posn.inpacket,num_bytes); return 1; } err = write_picture_data(output,as_TS,picture,reverse_data->pid); if (err) { print_err("### Error writing picture\n"); free_h262_picture(&picture); return 1; } free_h262_picture(&picture); } else { byte *data = NULL; uint32_t data_len = 0; err = read_ES_data(es,start_posn,num_bytes,&data_len,&data); if (err) { fprint_err("### Error reading data from " OFFSET_T_FORMAT "/%d for %d\n", start_posn.infile,start_posn.inpacket,num_bytes); return 1; } err = write_packet_data(output,as_TS,data,num_bytes,reverse_data->pid, reverse_data->stream_id); if (err) { print_err("### Error writing picture as TS PES packet\n"); free(data); return 1; } free(data); } // And let our "outer" contexts know which picture that *is* in the // sequence of pictures if (reverse_data->is_h264) reverse_data->h264->access_unit_index = reverse_data->index[which]; else reverse_data->h262->picture_index = reverse_data->index[which]; // Remember that we are now that bit further "back" in the reverse data // arrays, for when we come to move forwards again // (we only do this for pictures that have actually been *read*, since // it's only then that our file positions, etc., will have changed.) reverse_data->last_posn_added = which; return 0; } /* * Output the last picture (or an earlier one) from the reverse arrays. * This version writes the data out as Transport Stream. * * This is expected to be used after the whole of the data stream has been * played, so that the last picture in the reverse arrays is the last I or * IDR picture in the data stream. * * - `es` is the input elementary stream * - `tswriter` is the transport stream writer * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `offset` is the offset from the end of the array of the picture * to output - so 0 means the last picture, 1 the picture before that, * and so on. Sequence headers do not count for this purpose. * - `reverse_data` is the reverse data context. * * Returns 0 if all went well, 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int output_from_reverse_data_as_TS(ES_p es, TS_writer_p tswriter, int verbose, int quiet, uint32_t offset, reverse_data_p reverse_data) { WRITER writer; writer.ts_output = tswriter; return output_from_reverse_data(es,writer,TRUE,verbose,quiet,offset, reverse_data); } /* * Output the last picture (or an earlier one) from the reverse arrays. * This version writes the data out as Elementary Stream. * * This is expected to be used after the whole of the data stream has been * played, so that the last picture in the reverse arrays is the last I or * IDR picture in the data stream. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `offset` is the offset from the end of the array of the picture * to output - so 0 means the last picture, 1 the picture before that, * and so on. Sequence headers do not count for this purpose. * - `reverse_data` is the reverse data context. * * Returns 0 if all went well, 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int output_from_reverse_data_as_ES(ES_p es, FILE *output, int verbose, int quiet, uint32_t offset, reverse_data_p reverse_data) { WRITER writer; writer.es_output = output; return output_from_reverse_data(es,writer,FALSE,verbose,quiet,offset, reverse_data); } /* * Output the H.262 pictures or H.264 access units we remembered earlier - but * in reverse order. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `as_TS` is true, then output as TS packets, not ES * - if `frequency` is non-zero, then attempt to produce the effect of * keeping every th picture (similar to reversing at a * multiplication factor of `frequency`) If 0, just output all the * pictures that were remembered. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `start_with` is the index at which to start outputting from the * reverse data arrays. The value -1 may be used to indicate the most * recent picture in the arrays. If `start_with` is less than -1 then this * function will do nothing. If `start_with` is off the end of the * arrays, then reversing will start from the end of the arrays. * - if `max` is non-zero, then output will stop after at least `max` * pictures have been reversed past. * - `reverse_data` contains the list of pictures/access units to reverse. * * Returns 0 if all went well, COMMAND_RETURN_CODE if the current "command" * has changed, or 1 if something went wrong. */ static int output_in_reverse(ES_p es, WRITER output, int as_TS, int frequency, int verbose, int quiet, int32_t start_with, int max, reverse_data_p reverse_data) { int ii; int with_sequence_headers = reverse_data->output_sequence_headers; byte *data = NULL; // picture data, as a "chunk" uint32_t data_len = 0; // the current size of `data` h262_picture_p picture = NULL; // H.262 picture data as a "picture" uint32_t last_seq_index = reverse_data->length; // impossible value int max_pic_index = reverse_data->length-1; int first_actual_picture_index = 0; // the first *actual* picture int is_h262 = !reverse_data->is_h264; uint32_t start_index; uint32_t final_index; uint32_t last_index; uint32_t last_num_bytes = 0; // Number of bytes of last picture written reverse_data->pictures_written = 0; reverse_data->pictures_kept = 0; reverse_data->first_written = 0; reverse_data->last_written = 0; // Check we have some data to work with, so we don't try to index // nonexistant entries in the arrays if (reverse_data->length == 0) { if (!quiet) print_msg("No data to reverse\n"); return 0; } // What's the earliest *actual* picture (not a sequence header)? while (SEQUENCE_HEADER_ENTRY(reverse_data,first_actual_picture_index)) first_actual_picture_index ++; // Where did the user ask us to start? if (start_with < -1) return 0; else if (start_with == -1) start_index = reverse_data->last_posn_added; else if (start_with > max_pic_index) start_index = max_pic_index; else start_index = start_with; // Check that's not a sequence header - if it is, go back one while (start_index > 0 && SEQUENCE_HEADER_ENTRY(reverse_data,start_index)) start_index --; // If that means there's nothing to output, then so be it if (start_index < (uint32_t)first_actual_picture_index) return 0; // Remember the index of the latest picture we're interested in final_index = reverse_data->index[start_index]; // And the index of the last picture we output // - we carefully forge this so that the first (last) picture will be output last_index = final_index + frequency; reverse_data->first_written = start_index; if (verbose) fprint_msg("REVERSING: " "From index %d (picture %d) down to %d (%d), frequency %d, max %d\n", start_index,reverse_data->index[start_index], first_actual_picture_index, reverse_data->index[first_actual_picture_index],frequency,max); // If `frequency` is 0, we just want to output all the pictures, backwards. // Otherwise, we want to output the first picture we retrieve (i.e., the // last picture in the reverse data list), and then (effectively) output // a picture every `frequency` pictures. The reverse data `index` value // is the index of the picture as a picture of any type (i.e., including // the pictures that we didn't bother to remember). for (ii = start_index; ii >= first_actual_picture_index; ii--) { int err; int keep = FALSE; uint32_t index; ES_offset start_posn; uint32_t num_bytes; byte seq_offset; byte afd; uint32_t seq_index; if (as_TS && tswrite_command_changed(output.ts_output)) { if (data != NULL) free(data); if (picture != NULL) free_h262_picture(&picture); return COMMAND_RETURN_CODE; } err = get_reverse_data(reverse_data,ii,&index,&start_posn,&num_bytes, &seq_offset,&afd); if (err) return 1; if (verbose) fprint_msg("\nPicture [%03d] %4d from " OFFSET_T_FORMAT_08 "/%04d for %5d\n", ii,index,start_posn.infile,start_posn.inpacket,num_bytes); // Should we write this picture out? if (start_posn.infile < 0) { fprint_err("!!! Start position for reverse item %d does not make sense\n" " item %d, picture %d, start posn " OFFSET_T_FORMAT "/%d, num bytes %d, seq offset %d\n",ii,ii,index, start_posn.infile,start_posn.inpacket,num_bytes,seq_offset); print_err(" Ignoring item\n"); } else if (is_h262 && seq_offset == 0) { // Sequence headers get output (if at all) when their pictures // are written out if (verbose) print_msg(".. Sequence header - no need to write\n"); } else if (frequency != 0) { int gap = last_index - index; // gap since last picture output if (gap < frequency) { if (verbose) fprint_msg("++ %d/%d DROP: [%d] %d too soon\n", gap,frequency,ii,index); } else { // It's not too soon - but do we need to up our output frequency // by repeating the last picture? int pictures_seen = final_index - index; int pictures_wanted = pictures_seen / frequency; int repeat = pictures_wanted - reverse_data->pictures_written; if (verbose) fprint_msg("** Pictures seen = %d, wanted = %d, written = %d" " -> repeat = %d\n",pictures_seen,pictures_wanted, reverse_data->pictures_written,repeat); if (repeat > 0 && ((is_h262 && picture != NULL) || (!is_h262 && data != NULL))) { int jj; if (verbose) { if (repeat == 1) print_msg(">> repeating last picture\n"); else if (repeat > 1) fprint_msg(">> repeating last picture %d times\n",repeat); } for (jj=0; jjpid); else err = write_packet_data(output,as_TS,data,last_num_bytes, reverse_data->pid,reverse_data->stream_id); if (err) { print_err("### Error writing (picture) data\n"); if (data != NULL) free(data); if (picture != NULL) free_h262_picture(&picture); return 1; } reverse_data->pictures_written ++; } } keep = TRUE; if (verbose) fprint_msg("++ %d/%d KEEP: writing out\n",gap,frequency); last_index = index; } } else keep = TRUE; // i.e., because frequency == 0 // *But* always output the *first* picture, since if we reach it we've // "run out" of pictures to present if (ii == first_actual_picture_index) { if (verbose && !keep) print_msg("++ but KEEP first picture regardless\n"); keep = TRUE; } if (keep) { if (with_sequence_headers) { // Make sure we've output its sequence header seq_index = ii - seq_offset; if (seq_index != last_seq_index) { err = output_sequence_header(es,output,as_TS,verbose,(uint16_t)seq_index, reverse_data); if (err) { fprint_err("### Error retrieving sequence header" " for picture %d (offset %d)\n",ii,seq_offset); if (data != NULL) free(data); if (picture != NULL) free_h262_picture(&picture); return 1; } last_seq_index = seq_index; } } if (verbose) fprint_msg("Writing picture [%03d] %4d from " OFFSET_T_FORMAT_08 "/%04d for %5d\n", ii,index,start_posn.infile,start_posn.inpacket,num_bytes); if (is_h262) { if (picture != NULL) free_h262_picture(&picture); err = read_h262_picture(reverse_data->h262,start_posn,afd, verbose,&picture); if (err) { fprint_err("### Error reading H.262 picture from " OFFSET_T_FORMAT "/%d for %d\n", start_posn.infile,start_posn.inpacket,num_bytes); return 1; } err = write_picture_data(output,as_TS,picture,reverse_data->pid); if (err) { print_err("### Error writing picture\n"); free_h262_picture(&picture); return 1; } } else { err = read_ES_data(es,start_posn,num_bytes,&data_len,&data); if (err) { fprint_err("### Error reading data from " OFFSET_T_FORMAT "/%d for %d\n", start_posn.infile,start_posn.inpacket,num_bytes); if (data != NULL) free(data); return 1; } err = write_packet_data(output,as_TS,data,num_bytes,reverse_data->pid, reverse_data->stream_id); if (err) { print_err("### Error writing picture\n"); if (data != NULL) free(data); return 1; } last_num_bytes = num_bytes; } reverse_data->last_written = ii; reverse_data->pictures_written ++; reverse_data->pictures_kept ++; // And let our "outer" contexts know which picture that *is* in the // sequence of pictures if (reverse_data->is_h264) reverse_data->h264->access_unit_index = reverse_data->index[ii]; else reverse_data->h262->picture_index = reverse_data->index[ii]; // Remember that we are now that bit further "back" in the reverse data // arrays, for when we come to move forwards again // (we only do this for pictures that have actually been *read*, since // it's only then that our file positions, etc., will have changed.) reverse_data->last_posn_added = ii; if (verbose) fprint_msg("Last written [%03d], picture index %d, last_posn_added %d\n", ii,reverse_data->index[ii],ii); } if (max != 0 && (int)(final_index - index + 1) >= max) { if (verbose) fprint_msg("Break: max %d, final_index %d, index %d\n", max,final_index,index); break; } } if (data != NULL) free(data); if (picture != NULL) free_h262_picture(&picture); if (verbose) print_msg("END OF REVERSE\n"); return 0; } /* * Output the H.262 pictures or H.264 access units we remembered earlier - but * in reverse order. This version writes the data out as Transport Stream. * * - `es` is the input elementary stream * - `tswriter` is the transport stream writer * - if `frequency` is non-zero, then attempt to produce the effect of * keeping every th picture (similar to reversing at a * multiplication factor of `frequency`) If 0, just output all the * pictures that were remembered. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `start_with` is the index at which to start outputting from the * reverse data arrays. The value -1 may be used to indicate the most * recent picture in the arrays. If `start_with` is less than -1 then this * function will do nothing. If `start_with` is off the end of the * arrays, then reversing will start from the end of the arrays. * - if `max` is non-zero, then output will stop after at least `max` * pictures have been reversed past. * - `reverse_data` contains the list of pictures/access units to reverse. * * Returns 0 if all went well, 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int output_in_reverse_as_TS(ES_p es, TS_writer_p tswriter, int frequency, int verbose, int quiet, int32_t start_with, int max, reverse_data_p reverse_data) { WRITER writer; writer.ts_output = tswriter; return output_in_reverse(es,writer,TRUE,frequency,verbose,quiet, start_with,max,reverse_data); } /* * Output the H.262 pictures or H.264 access units we remembered earlier - but * in reverse order. This version writes the data out as Elementary Stream. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `frequency` is non-zero, then attempt to produce the effect of * keeping every th picture (similar to reversing at a * multiplication factor of `frequency`) If 0, just output all the * pictures that were remembered. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `start_with` is the index at which to start outputting from the * reverse data arrays. The value -1 may be used to indicate the most * recent picture in the arrays. If `start_with` is less than -1 then this * function will do nothing. If `start_with` is off the end of the * arrays, then reversing will start from the end of the arrays. * - if `max` is non-zero, then output will stop after at least `max` * pictures have been reversed past. * - `reverse_data` contains the list of pictures/access units to reverse. * * Returns 0 if all went well, 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int output_in_reverse_as_ES(ES_p es, FILE *output, int frequency, int verbose, int quiet, int32_t start_with, int max, reverse_data_p reverse_data) { WRITER writer; writer.es_output = output; return output_in_reverse(es,writer,FALSE,frequency,verbose,quiet, start_with,max,reverse_data); } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/reverse_defns.h000066400000000000000000000134201261471605300176730ustar00rootroot00000000000000/* * Support for reversing * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _reverse_defns #define _reverse_defns #include "compat.h" #include "es_defns.h" // Since reverse_data refers to h262 and acces_unit datastructures, and // *they* refer to reverse_data, we need to break the circular referencing // at some point typedef struct reverse_data *reverse_data_p; #include "h262_defns.h" #include "accessunit_defns.h" // ------------------------------------------------------------ // As the software progresses through the data stream forwards, it remembers // the location, size and details for frames that it might want to output in // reverse struct reverse_data { int is_h264; // Dealing with H.264 or H.262 data? // To do anything useful, we must then be linked to one or the other // type of input context (a union would save 4 bytes, but it doesn't // seem worth the bother at the moment). h262_context_p h262; access_unit_context_p h264; // Information for managing our arrays. `use_seq_offset` will be TRUE // for H.262 data, and FALSE for H.264 (MPEG-4/AVC) int length; // Number of items in our arrays int size; // How big our arrays are uint32_t num_pictures; // How many pictures we have // Four useful arrays (although the last is not used for H.264 data) uint32_t *index; // Which picture this is, counted from the start offset_t *start_file; // The start offset of an item in the input file int32_t *start_pkt; // and then within the PES packet (if needed) int32_t *data_len; // Its length in bytes byte *seq_offset; // For MPEG-2, the offset backwards in the arrays // to the nearest earlier sequence header, or 0 // for a sequence header entry byte *afd_byte; // For MPEG-2, the AFD byte current for the picture // @@@ To be added later: for H.264 it's useful to know if a particular // entry is an IDR or not. Thus add a ``byte *`` value called something // like `is_IDR`, and make it point to `seq_offset`, since that is // not used for H.264 data. // (as a precursor to this, the code in reverse.c already allocates // and extends the seq_offset array whether it is H.262 or H.264 data) // Is our "counting" in `index` going to last long enough? Well, if // we assume (worst case) that every picture was remembered in our arrays, // then we would have 2**32-1 pictures. At (another worst case) 50 frames // per second, that gives us (2**32-1)/50 seconds, which is 85899345 // seconds (rounded down), or 23860 hours. Which I think should be enough // (about two years - yes, that should be enough). // When a function (output_in_reverse_as_XX) is called to output // reversed data, the reversal can either start from a specific index, // or from the "default", which is the current index. To make this easier, // we need to remember the index of the last entry added (which may be // less than the length of the array, since we might have rewound, and // be adding pictures again). Note that this is undefined if `length` // is 0. // // (There's probably a better name for this, since it's really more // like the highwater mark for what we've played forwards since we last // started playing forwards again, but I can't think of anything startlingly // illustrative, so this name will stay for now.) uint32_t last_posn_added; // Do we want to output sequence headers or not, when reversing H.262? int output_sequence_headers; // If we're outputting TS packets, we need to know the PID and stream id // to use. We *could* pass that down to each reverse call, but it's easier // to set it once and for all. uint32_t pid; byte stream_id; // When a function (output_in_reverse_as_XX) is called to output // reversed data, some statistics are maintained by the call. // Despite saying "picture", these also apply to H.264 access units as well // (and in fact "frame" would be a better term). int pictures_written; // The number of pictures written out int pictures_kept; // The number of *different* pictures written int first_written; // Which picture was the first written out int last_written; // Which picture was the last written out // (for both of the latter, the value is the index of said picture in // our arrays). Remember that if forwards action is to be made after // reversing, it is important to reset the picture index in the H.262 // or access_unit context to the picture index of the last written // picture. This must be done by the caller. }; #define SIZEOF_REVERSE_DATA sizeof(struct reverse_data) #define REVERSE_ARRAY_START_SIZE 1000 #define REVERSE_ARRAY_INCREMENT_SIZE 500 #endif // _reverse_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/reverse_fns.h000066400000000000000000000361471261471605300173750ustar00rootroot00000000000000/* * Support for reversing * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _reverse_fns #define _reverse_fns #include "accessunit_defns.h" #include "reverse_defns.h" #include "h262_defns.h" /* * Build the internal arrays to remember video sequence bounds in, * for reversing. * * Builds a new `reverse_data` datastructure. If `is_h264` is FALSE (i.e., the * data to be reversed is not MPEG-1 or MPEG-2), then this datastructure may * be smaller. * * To collect reversing data, attach this datastructure to an H.262 or access * unit context (with add_h262/access_unit_reverse_context), and then use * get_next_h262_frame() or get_next_h264_frame() to read through the data * stream - appropriate pictures/access units will be remembered * automatically. * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int build_reverse_data(reverse_data_p *reverse_data, int is_h264); /* * Set the video PID and stream id for TS output. * * This need only be called if reverse data *is* being output as TS, * and if the standard default values (DEFAULT_VIDEO_PID and * DEFAULT_VIDEO_STREAM_ID) are not correct. */ extern void set_reverse_pid(reverse_data_p reverse_data, uint32_t pid, byte stream_id); /* * Add a reversing context to an H.262 context (and vice versa). * * Does not check if there is one present already. * * Returns 0 if all is well, 1 if something goes wrong. */ extern int add_h262_reverse_context(h262_context_p h262, reverse_data_p reverse_data); /* * Add a reversing context to an access unit context (and vice versa). * * Does not check if there is one present already. * * Returns 0 if all is well, 1 if something goes wrong. */ extern int add_access_unit_reverse_context(access_unit_context_p context, reverse_data_p reverse_data); /* * Free the datastructure we used to remember reversing data * * Sets `reverse_data` to NULL. */ extern void free_reverse_data(reverse_data_p *reverse_data); /* * Remember video sequence bounds for H.262 data * * - `reverse_data` is the datastructure we want to add our entry to * - `index` indicates which picture (counted from the start of the file) * this one is (i.e., we're assuming that not all pictures will be stored). * If the entry is an H.262 sequence header, then this is ignored. * - `start_posn` is the location of the start of the entry in the file, * The entry will be ignored if `start_posn` comes before the last * existing entry in the arrays. * - `length` is the number of bytes in the entry * - in H.262 data, `seq_offset` should be 0 for a sequence header, and is * otherwise the offset backwards to the previous nearest sequence header * (i.e., 1 if the sequence header is the previous entry). * - `afd` is the effective AFD byte for this picture * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remember_reverse_h262_data(reverse_data_p reverse_data, uint32_t index, ES_offset start_posn, uint32_t length, byte seq_offset, byte afd); /* * Remember video sequence bounds for H.264 data * * - `reverse_data` is the datastructure we want to add our entry to * - `index` indicates which picture (counted from the start of the file) * this one is (i.e., we're assuming that not all pictures will be stored). * If the entry is an H.262 sequence header, then this is ignored. * - `start_posn` is the location of the start of the entry in the file, * The entry will be ignored if `start_posn` comes before the last * existing entry in the arrays. * - `length` is the number of bytes in the entry * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int remember_reverse_h264_data(reverse_data_p reverse_data, uint32_t index, ES_offset start_posn, uint32_t length); /* * Retrieve video sequence bounds for entry `which` * * - `reverse_data` is the datastructure we want to get our entry from * - `which` indicates which entry we'd like to retrieve. The first * entry in the `reverse_data` is number 0. * - `index` indicates which picture (counted from the start of the file) * this one is (i.e., we're assuming that not all pictures will be stored). * `index` may be passed as NULL if the value is of no interest - i.e., * typically when the entry is for an H.262 sequence header. * - `start_posn` is the location of the start of the entry in the file, * - `length` is the number of bytes in the entry * - for H.262 data, if the entry is a picture, then `seq_offset` will * be the offset backwards to the previous nearest sequence header * (i.e., 1 if the sequence header is the previous entry), and if it is * a sequence header, `seq_offset` will be 0. For H.264 data, the value * will always be 0. `seq_offset` may be passed as NULL if the value is * of no interest. * - for H.262 data, if the entry is a picture, then `afd` will be its * (effective) AFD byte. Otherwise it will be 0. `afd` may be passed as NULL * if the value if of no interest. * * To clarify, all of the following are legitimate calls:: * * err = get_reverse_data(reverse_data,10,&index,&start,&length,&offset,&afd); * err = get_reverse_data(reverse_data,10,&index,&start,&length,NULL,NULL); * err = get_reverse_data(reverse_data,10,NULL,&start,&length,NULL,NULL); * * Returns 0 if it succeeds, 1 if some error occurs. */ extern int get_reverse_data(reverse_data_p reverse_data, int which, uint32_t *index, ES_offset *start_posn, uint32_t *length, byte *seq_offset, byte *afd); // ============================================================ // Collecting pictures // ============================================================ /* * Locate and remember sequence headers and I pictures, for later reversal. * * - `h262` is the H.262 stream reading context * - if `max` is non-zero, then collecting will stop after `max` pictures * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if all went well, EOF if the end of file is reached, * and 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int collect_reverse_h262(h262_context_p h262, int max, int verbose, int quiet); /* * Find IDR and I slices, and remember their access units for later output * in reverse order. * * - `acontext` is the access unit reading context * - if `max` is non-zero, then collecting will stop after `max` access units * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if all went well, EOF if the end of file is reached, * and 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int collect_reverse_access_units(access_unit_context_p acontext, int max, int verbose, int quiet); /* * Output the last picture (or an earlier one) from the reverse arrays. * This version writes the data out as Transport Stream. * * This is expected to be used after the whole of the data stream has been * played, so that the last picture in the reverse arrays is the last I or * IDR picture in the data stream. * * - `es` is the input elementary stream * - `tswriter` is the transport stream writer * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `offset` is the offset from the end of the array of the picture * to output - so 0 means the last picture, 1 the picture before that, * and so on. Sequence headers do not count for this purpose. * - `reverse_data` is the reverse data context. * * Returns 0 if all went well, 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int output_from_reverse_data_as_TS(ES_p es, TS_writer_p tswriter, int verbose, int quiet, uint32_t offset, reverse_data_p reverse_data); /* * Output the last picture (or an earlier one) from the reverse arrays. * This version writes the data out as Elementary Stream. * * This is expected to be used after the whole of the data stream has been * played, so that the last picture in the reverse arrays is the last I or * IDR picture in the data stream. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `offset` is the offset from the end of the array of the picture * to output - so 0 means the last picture, 1 the picture before that, * and so on. Sequence headers do not count for this purpose. * - `reverse_data` is the reverse data context. * * Returns 0 if all went well, 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int output_from_reverse_data_as_ES(ES_p es, FILE *output, int verbose, int quiet, uint32_t offset, reverse_data_p reverse_data); /* * Output the H.262 pictures or H.264 access units we remembered earlier - but * in reverse order. This version writes the data out as Transport Stream. * * - `es` is the input elementary stream * - `tswriter` is the transport stream writer * - if `frequency` is non-zero, then attempt to produce the effect of * keeping every th picture (similar to reversing at a * multiplication factor of `frequency`) If 0, just output all the * pictures that were remembered. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `start_with` is the index at which to start outputting from the * reverse data arrays. The value -1 may be used to indicate the most * recent picture in the arrays. If `start_with` is less than -1 then this * function will do nothing. If `start_with` is off the end of the * arrays, then reversing will start from the end of the arrays. * - if `max` is non-zero, then output will stop after at least `max` * pictures have been reversed past. * - `reverse_data` contains the list of pictures/access units to reverse. * * Returns 0 if all went well, 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int output_in_reverse_as_TS(ES_p es, TS_writer_p tswriter, int frequency, int verbose, int quiet, int32_t start_with, int max, reverse_data_p reverse_data); /* * Output the H.262 pictures or H.264 access units we remembered earlier - but * in reverse order. This version writes the data out as Elementary Stream. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `frequency` is non-zero, then attempt to produce the effect of * keeping every th picture (similar to reversing at a * multiplication factor of `frequency`) If 0, just output all the * pictures that were remembered. * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * - `start_with` is the index at which to start outputting from the * reverse data arrays. The value -1 may be used to indicate the most * recent in the arrays. If `start_with` is less than -1 then this * function will do nothing. If `start_with` is off the end of the * arrays, then reversing will start from the end of the arrays. * - if `max` is non-zero, then output will stop after at least `max` * pictures have been reversed past. * - `reverse_data` contains the list of pictures/access units to reverse. * * Returns 0 if all went well, 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ extern int output_in_reverse_as_ES(ES_p es, FILE *output, int frequency, int verbose, int quiet, int32_t start_with, int max, reverse_data_p reverse_data); #endif // _reverse_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/rtp2264.c000066400000000000000000000146531261471605300161700ustar00rootroot00000000000000/* * Filter a transport stream by a list of pids. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ // H.264 over RTP is defined in RFC3984 #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif // _WIN32 #include "compat.h" #include "version.h" #include "misc_fns.h" #include "fmtx.h" #define RTP_HDR_LEN 8 #define RTP_PREFIX_STRING "RTP " #define RTP_PREFIX_LEN 4 #define RTP_LEN_OFFSET 4 static int c642b(const char c) { return (c >= 'A' && c <= 'Z') ? c - 'A' : (c >= 'a' && c <= 'z') ? c - 'a' + 26 : (c >= '0' && c <= '9') ? c - '0' + 52 : (c == '+' || c == '-') ? 62 : (c == '/' || c == '_') ? 63 : (c == '=') ? -1 : -2; } static size_t b64str2binn(byte * const dest0, const size_t dlen, const char ** const plast, const char * src) { byte * dest = dest0; uint32_t a = 0; ssize_t i = 4; size_t slen = (dlen * 4 + 5) / 3; int b; while ((b = c642b(*src++)) >= 0 && --slen != 0) { a = (a << 6) | b; if (--i == 0) { *dest++ = (a >> 16) & 0xff; *dest++ = (a >> 8) & 0xff; *dest++ = a & 0xff; i = 4; } } // Tidy up at the end if (i < 3) // i == 4 good, all done, i == 3 error { a <<= i * 6; *dest++ = (a >> 16) & 0xff; // Consume '=' if (b == -1) b = c642b(*src++); if (i == 1) { *dest++ = (a >> 8) & 0xff; } else if (b == -1) ++src; } if (plast != NULL) *plast = src - 1; return dest - dest0; } static void usage(void) { print_msg( "Usage: rtp2264 [[,...]]\n" "\n"); REPORT_VERSION("rtp2264"); print_msg( "\n" " Take a RTP file (probably generated by pcapreport) containing\n" " an H.264 stream and convert it into an Annex B encoded .264\n" " elementary stream file.\n" "\n" " If s are specified then they are one or more\n" " B64 encoded blocks containing SPS or PPS or other similar headers.\n" " These will each have a 00 00 00 01 sequence added at the start and\n" " then written at the start of \n" "\n" "Switches:\n" " none\n" ); exit(1); } int main(int argc, char **argv) { FILE *f_in = NULL; FILE *f_out = NULL; const char * fname_in; const char * fname_out; int zcount = 0; if (argc < 3) { usage(); } fname_in = argv[1]; fname_out = argv[2]; if ((f_in = fopen(fname_in, "rb")) == NULL) { perror(argv[1]); return 1; } if ((f_out = fopen(fname_out, "wb")) == NULL) { perror(argv[2]); return 1; } if (argc > 3) { byte psbuf[0x1000]; const char * eo64 = argv[3]; psbuf[0] = 0; psbuf[1] = 0; psbuf[2] = 0; psbuf[3] = 1; do { size_t len = b64str2binn(psbuf + 4, sizeof(psbuf) - 4, &eo64, eo64); if ((*eo64 != 0 && *eo64 != ',') || len == 0) { fprintf(stderr, "Bad B64 string: '%s' (len=%zd, chr=%d)\n", argv[3], len, *eo64); exit(1); } if (fwrite(psbuf, len + 4, 1, f_out) != 1) { perror(fname_out); exit(1); } } while (*eo64++ == ','); } for (;;) { byte buf[0x10000]; uint32_t rtplen; if (fread(buf, RTP_HDR_LEN, 1, f_in) != 1) { if (ferror(f_in)) perror(fname_in); break; } if (memcmp(buf, RTP_PREFIX_STRING, RTP_PREFIX_LEN) != 0) { fprintf(stderr, "### Bad RTP prefix\n"); break; } rtplen = uint_32_be(buf + RTP_LEN_OFFSET); if (rtplen > sizeof(buf) || rtplen < 12) { fprintf(stderr, "### Bad RTP len: %" PRIu32 "\n", rtplen); break; } if (fread(buf, rtplen, 1, f_in) != 1) { if (ferror(f_in)) perror(fname_in); else fprintf(stderr, "### Unexpected EOF\n"); break; } { size_t offset = 12 + (buf[0] & 0xf) * 4; size_t padlen = ((buf[0] & 0x20) != 0) ? buf[rtplen - 1] : 0; // Check for extension if ((buf[0] & 0x10) != 0) // X bit offset += 4 + uint_16_be(buf + offset + 2); if (rtplen < offset + padlen + 1) { fprintf(stderr, "### Bad RTP offset + padding\n"); } // OK - got payload { const byte * p = buf + offset; const byte * p_end = buf + rtplen - padlen; byte buf2[0x18000]; // Allow for max expansion byte * q = buf2; byte sc1 = *p++; if ((sc1 & 0x1f) == 28) { byte sc2 = *p++; if ((sc2 & 0x80) != 0) // S bit { // Start of fragmented unit sc1 = (sc1 & 0xe0) | (sc2 & 0x1f); *q++ = 0; *q++ = 0; *q++ = 0; *q++ = 1; *q++ = sc1; zcount = 0; printf("Fragmented block with code: %x\n", sc1); } } else { // Normal start code *q++ = 0; *q++ = 0; *q++ = 0; *q++ = 1; *q++ = sc1; zcount = 0; printf("Start block with code: %x\n", sc1); } // Engage emulation protect while (p < p_end) { const byte b = *p++; if (zcount == 2 && b <= 3) { *q++ = 3; zcount = 0; } *q++ = b; zcount = (b == 0) ? zcount + 1 : 0; } if (fwrite(buf2, q - buf2, 1, f_out) != 1) { perror(fname_out); exit(1); } } } } fclose(f_out); fclose(f_in); return 0; } tstools-1.13~git20151030/sockread.py000066400000000000000000000045461261471605300170460ustar00rootroot00000000000000#! /usr/bin/env python """sockread.py -- a simple client to read from a socket """ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Amino Communications Ltd, Swavesey, Cambridge UK # # ***** END LICENSE BLOCK ***** import sys import socket class DoneException(Exception): pass def get_packet(sock,packet_size=188): """Read a packet from the socket, coping with partial reads. """ data = "" total = 0 while total < packet_size: data += sock.recv(packet_size - total) if len(data) == 0: raise DoneException total += len(data) return data def read_next_packet(sock,f=None): """Read the next packet from the socket, checking and counting it. """ data = get_packet(sock) if ord(data[0]) == 0x47 and len(data) == 188: sys.stdout.write(".") else: sys.stdout.write("[%x]/%d"%(ord(data[0]),len(data))) sys.stdout.flush() if f: f.write(data) def main(): total_packets = 0 sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) print "Waiting on port 8889" sock.bind(("localhost",8889)) sock.listen(1) conn, addr = sock.accept() print 'Connected by', addr #print "Writing to file temp.ts" #stream = file("temp.ts","wb") stream = None try: while 1: read_next_packet(conn,stream) total_packets += 1 except DoneException: #stream.close() pass sys.stdout.write("\n") sys.stdout.write("Total packets: %d\n"%total_packets) sock.close() if __name__ == "__main__": # try: main() # except KeyboardInterrupt: # print tstools-1.13~git20151030/socktest.py000066400000000000000000000146541261471605300171130ustar00rootroot00000000000000#! /usr/bin/env python """socktest.py -- a simple client to talk to tsserve Command line - optionally: -host defaults to "localhost" -port defaults to 8889 -output defaults to None -file the same -nonblock the socket should operate in non-blocking mode followed by zero or more commands, specified as: for n, F, f, r or R or for any of the above, and also q, p, > and <, or 0 .. 9 If a is given, it is the number of data packets to try to read before issuing the next command. The commands "n", "f", "F", r" and "R", and the "select channel and play" commands "0" .. "9" may be given a count, in which case the command is given and then that many data packets are read before the next command is given. If no count is given, then data packets are read "forever". The commands "p", ">" and "<" do not take a count, and do not read any data packets. The command "q" does not take a count, but reads the rest of the data packets. If no commands are given, the default is "n" with no count. """ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the MPEG TS, PS and ES tools. # # The Initial Developer of the Original Code is Amino Communications Ltd. # Portions created by the Initial Developer are Copyright (C) 2008 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Amino Communications Ltd, Swavesey, Cambridge UK # # ***** END LICENSE BLOCK ***** import sys import socket class DoneException(Exception): pass global total_packets def get_packet(sock,packet_size=188): """Read a packet from the socket, coping with partial reads. """ data = "" total = 0 while total < packet_size: data += sock.recv(packet_size - total) if len(data) == 0: raise DoneException total += len(data) return data def read_next_packet(sock,file=None): """Read the next packet from the socket, checking and counting it. """ data = get_packet(sock) if ord(data[0]) == 0x47 and len(data) == 188: sys.stdout.write(".") else: sys.stdout.write("[%x]/%d"%(ord(data[0]),len(data))) sys.stdout.flush() global total_packets total_packets += 1 if file: file.write(data) def give_command(sock,command="n",file=None,howmany=None): """Give the command specified, and then read data packets. If `howmany` is specified, try to read that many packets (and return thereafter), otherwise, just keep trying to read. Raises DoneException if there is no more data to read. """ if howmany is None: print "Sending command '%s' and listening"%command else: print "Sending command '%s' and listening for %d packets"%(command, howmany) sock.send(command) if howmany is None: while 1: read_next_packet(sock,file) else: try: for count in range(howmany): read_next_packet(sock,file) except DoneException: sys.stdout.write("\n") sys.stdout.write("Finished listening after %d packets"%count) raise DoneException except socket.error, val: print "socket.error:",val raise DoneException print def main(): global total_packets total_packets = 0 host = "localhost" port = 8889 stream = filename = None nonblock = 0 argv = sys.argv[1:] if len(argv) == 0: print __doc__ return while len(argv) > 0 and argv[0].startswith("-"): if argv[0] in ("-h", "-help", "--help"): print __doc__ return elif argv[0] == "-host": host = argv[1] argv = argv[2:] elif argv[0] == "-port": port = int(argv[1]) argv = argv[2:] elif argv[0] in ("-file", "-output"): filename = argv[1] argv = argv[2:] elif argv[0] in ("-nonblock"): nonblock = 1 argv = argv[1:] else: print "Unexpected switch",argv[0] return commands = [] if len(argv) == 0: print "No commands specified - assuming 'n'ormal play" commands = [("n",None)] command = None count = 0 for word in argv: if command: # we have a command waiting for a count try: count = int(word) except: print "'%s' does not work as a count for command '%s'"%(word,command) return commands.append((command,count)) command = None elif word in ("p", ">", "<"): # commands that don't take a count commands.append((word,0)) command = None elif word in ("q"): # commands that read the rest of input commands.append((word,None)) command = None elif word in ("n","F","f","r","R", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"): # commands that do take a count command = word else: print "Unrecognised command '%s'"%word if command: commands.append((command,None)) print "Commands:", commands sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) print "Connecting to %s on port %d"%(host,port) sock.connect((host,port)) if filename: print "Writing output to file %s"%filename stream = file(filename,"wb") if nonblock: sock.setblocking(0) try: for command,count in commands: give_command(sock,command=command,file=stream,howmany=count) except (KeyboardInterrupt, DoneException): if stream: stream.close() sys.stdout.write("\n") sys.stdout.write("Total packets: %d\n"%total_packets) sock.close() if __name__ == "__main__": try: main() except KeyboardInterrupt: print tstools-1.13~git20151030/stream_type.c000066400000000000000000000330001261471605300173640ustar00rootroot00000000000000/* * Attempt to determine if an input stream is Transport Stream or Elementary * Stream, and if the latter, if it is H.262 or H.264 (MPEG-2 or MPEG-4/AVC). * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "es_fns.h" #include "ts_fns.h" #include "nalunit_fns.h" #include "h262_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" #define STREAM_IS_TS 10 #define STREAM_IS_PS 11 #define STREAM_IS_H262 12 #define STREAM_IS_H264 14 #define STREAM_IS_AVS 15 #define STREAM_MAYBE_PES 5 #define STREAM_IS_UNSURE 9 #define STREAM_IS_ERROR 0 /* * Look for an initial 0x47 sync byte, and check for TS * * Returns 0 if nothing went wrong, 1 if something did. */ static int check_if_TS(int input, byte cur_byte, int verbose, int *decided, int *result) { int ii; if (verbose) print_msg("Is it Transport Stream?\n"); // It may be enough to look at the first byte of the stream if (cur_byte != 0x47) { if (verbose) fprint_msg(" First byte in file is 0x%02X not 0x47, so it is not\n",cur_byte); return 0; } // Transport Stream packets start with 0x47, so it's a good bet. if (verbose) print_msg(" First byte in file is 0x47, so it looks like Transport Stream\n"); // To check a bit, we can try looking at every 188th byte if (verbose) print_msg(" Checking next 500 packets to see if they start 0x47\n"); for (ii=0; ii<500; ii++) { byte buf[TS_PACKET_SIZE]; int err = read_bytes(input,TS_PACKET_SIZE,buf); if (err) { fprint_err("### %s trying to read start of packet %d\n", (err==EOF?"EOF":"Error"),ii+1); return 1; } if (buf[TS_PACKET_SIZE-1] != 0x47) { if (verbose) fprint_msg(" Packet %d does not start with 0x47 (%02x instead)\n", ii+1,buf[TS_PACKET_SIZE-1]); return 0; } } if (verbose) print_msg("The checked packets all start with 0x47 - looks like TS\n"); *decided = TRUE; *result = STREAM_IS_TS; return 0; } /* * Try to decide if we *have* got program stream * * Returns 0 if nothing went wrong, 1 if something did. */ static int check_if_PS(int input, int verbose, int *decided, int *result) { int err; byte buf[10]; int stuffing_length; if (verbose) { print_msg("Is it Program Stream?\n"); print_msg(" Trying to read pack header\n"); } err = read_bytes(input,4,buf); if (err) { fprint_err("### %s trying to read start of first PS packet\n", (err==EOF?"EOF":"Error")); return 1; } if (buf[0] != 0 || buf[1] != 0 || buf[2] != 1 || buf[3] != 0xba) { if (verbose) fprint_msg(" File starts %02X %02X %02X %02X, not 00 00 01 BA - not PS\n", buf[0],buf[1],buf[2],buf[3]); return 0; } if (verbose) print_msg(" File starts 00 00 01 BA - could be PS," " reading pack header body\n"); err = read_bytes(input,8,buf); if (err) { fprint_err("### %s trying to read body of PS pack header\n", (err==EOF?"EOF":"Error")); return 1; } if ((buf[0] & 0xF0) == 0x20) { if (verbose) print_msg(" Looks like ISO/IEC 11171-1/MPEG-1 pack header\n"); } else if ((buf[0] & 0xC0) == 0x40) { if (verbose) print_msg(" Looks like ISO/IEC 13818-1/H.222.0 pack header\n"); err = read_bytes(input,2,&(buf[8])); if (err) { fprint_err("### %s trying to read last 2 bytes of body of PS pack header\n", (err==EOF?"EOF":"Error")); return 1; } stuffing_length = buf[9] & 0x07; // And ignore that many stuffing bytes... if (stuffing_length > 0) { err = read_bytes(input,stuffing_length,buf); if (err) { fprint_err("### %s trying to read PS pack header stuffing bytes\n", (err==EOF?"EOF":"Error")); return 1; } } } // We could check for reserved bits - maybe at another time if (verbose) print_msg(" OK, trying to read start of next packet\n"); err = read_bytes(input,4,buf); if (err) { fprint_err("### %s trying to read start of next PS packet\n", (err==EOF?"EOF":"Error")); return 1; } if (buf[0] != 0 || buf[1] != 0 || buf[2] != 1) { if (verbose) fprint_msg(" Next 'packet' starts %02X %02X %02X, not 00 00 01 - not PS\n", buf[0],buf[1],buf[2]); return 0; } if (verbose) print_msg(" Start of second packet found at right place - looks like PS\n"); *decided = TRUE; *result = STREAM_IS_PS; return 0; } /* * Look at the start of our "elementary" stream, and try to determine * its actual type. * * - `input` is the input stream to inspect * - if `verbose` is true, the caller wants details of how the decision * is being made * - `decided` is returned TRUE if the function believes it has identified * the stream type, in which case: * - `result` will an appropriate value indicating what we've decided * * Note that this function reads into the stream, and may attempt to * rewind it. * * Returns 0 if nothing went wrong, 1 if an error occurred */ static int determine_packet_type(int input, int verbose, int *decided, int *result) { int err; #ifdef _WIN32 int length; #else ssize_t length; #endif byte first_byte; int video_type; length = read(input,&first_byte,1); if (length == 0) { print_err("### EOF reading first byte\n"); return 1; } else if (length == -1) { fprint_err("### Error reading first byte: %s\n",strerror(errno)); return 1; } // Does it look like transport stream? err = check_if_TS(input,first_byte,verbose,decided,result); if (err) { close_file(input); return 1; } if (*decided) { close_file(input); return 0; } seek_file(input,0); // Does it look like program stream? err = check_if_PS(input,verbose,decided,result); if (err) { close_file(input); return 1; } if (*decided) { close_file(input); return 0; } seek_file(input,0); // Does it look like one of the types of ES we recognise? if (verbose) print_msg("Is it an Elementary Stream we recognise?\n"); err = decide_ES_file_video_type(input,!verbose,verbose,&video_type); if (err) { close_file(input); return 1; } switch (video_type) { case VIDEO_H264: *result = STREAM_IS_H264; *decided = TRUE; break; case VIDEO_H262: *result = STREAM_IS_H262; *decided = TRUE; break; case VIDEO_AVS: *result = STREAM_IS_AVS; *decided = TRUE; break; case VIDEO_UNKNOWN: *result = STREAM_IS_UNSURE; *decided = FALSE; if (verbose) print_msg("Still not sure\n"); break; default: fprint_msg("### stream_type: Unexpected decision from" " decide_ES_file_video_type: %d\n",video_type); close_file(input); return 1; } close_file(input); return 0; } static void print_usage() { print_msg( "Usage: stream_type [switches] \n" "\n" ); REPORT_VERSION("stream_type"); print_msg( "\n" " Attempt to determine if an input stream is Transport Stream,\n" " Program Stream, or Elementary Stream, and if the latter, if it\n" " is H.262 or H.264 (i.e., MPEG-2 or MPEG-4/AVC respectively)." "\n" " The mechanisms used are fairly crude, assuming that:\n" " - data is byte aligned\n" " - for TS, the first byte in the file will be the start of a NAL unit,\n" " and PAT/PMT packets will be findable\n" " - for PS, the first packet starts immediately at the start of the\n" " file, and is a pack header\n" " - if the first 1000 packets could be H.262 *or* H.264, then the data\n" " is assumed to be H.264 (the program doesn't try to determine\n" " sensible sequences of H.262/H.264 packets, so this is a reasonable\n" " way of guessing)\n" "\n" " It is quite possible that data which is not relevant will be\n" " misidentified\n" "\n" " The program exit value is:\n" " * 10 if it detects Transport Stream,\n" " * 11 if it detects Program Stream,\n" " * 12 if it detects Elementary Stream containing H.262 (MPEG-2),\n" " * 14 if it detects Elementary Stream containing H.264 (MPEG-4/AVC),\n" " * 5 if it looks like it might be PES,\n" " * 9 if it really cannot decide, or\n" " * 0 if some error occurred\n" "\n" "Files:\n" " is the file to analyse\n" "\n" "Switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -verbose, -v Output more detailed information about how it is\n" " making its decision\n" " -quiet, -q Only output error messages\n" ); } int main(int argc, char **argv) { char *input_name = NULL; int had_input_name = FALSE; int input = -1; int verbose = FALSE; int quiet = FALSE; int err = 0; int ii = 1; int decided = FALSE; int result = STREAM_IS_ERROR; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return STREAM_IS_ERROR; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-err",argv[ii])) { CHECKARG("stream_type",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### stream_type: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else { fprint_err("### stream_type: " "Unrecognised command line switch '%s'\n",argv[ii]); return STREAM_IS_ERROR; } } else { if (had_input_name) { fprint_err("### stream_type: Unexpected '%s'\n",argv[ii]); return STREAM_IS_ERROR; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### stream_type: No input file specified\n"); return STREAM_IS_ERROR; } input = open_binary_file(input_name,FALSE); if (input == -1) { fprint_err("### stream_type: Unable to open input file %s\n", input_name); return 1; } if (!quiet) fprint_msg("Reading from %s\n",input_name); // Try to guess err = determine_packet_type(input,verbose,&decided,&result); if (err) { print_err("### Unable to decide on stream type due to error\n"); return STREAM_IS_ERROR; } if (!quiet) { if (!decided) { print_msg("Unable to decide\n"); result = STREAM_IS_UNSURE; } else { switch (result) { case STREAM_IS_TS: print_msg("It appears to be Transport Stream\n"); break; case STREAM_IS_PS: print_msg("It appears to be Program Stream\n"); break; case STREAM_IS_H262: print_msg("It appears to be Elementary Stream, MPEG-2 (H.262)\n"); break; case STREAM_IS_H264: print_msg("It appears to be Elementary Stream, MPEG-4 (H.264)\n"); break; case STREAM_IS_AVS: print_msg("It appears to be Elementary Stream, AVS\n"); break; case STREAM_MAYBE_PES: print_msg("It looks likely to be PES\n"); break; case STREAM_IS_UNSURE: print_msg("It is not recognised\n"); break; default: fprint_msg("Unexpected decision value %d\n",result); result = STREAM_IS_ERROR; break; } } } return result; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/test_es_unit_list.c000066400000000000000000000075051261471605300206030ustar00rootroot00000000000000/* * A simple test for the ES unit lists from es.c * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include "compat.h" #include "es_fns.h" int main(int argc, char **argv) { int err, ii; int max = ES_UNIT_LIST_START_SIZE + ES_UNIT_LIST_INCREMENT + 3; ES_unit_list_p list = NULL; ES_unit_p unit = NULL; printf("Testing ES unit list\n"); printf("Test 1 - differing ES units\n"); err = build_ES_unit_list(&list); if (err) { printf("Test failed - constructing list\n"); return 1; } for (ii=0; iilength > list->size) { printf("Test failed - list length = %d, size = %d\n", list->length,list->size); return 1; } else if (list->length != ii+1) { printf("Test failed - list length is %d, expected %d\n", list->length,ii+1); return 1; } free_ES_unit(&unit); } printf("Test 1 - resetting list\n"); reset_ES_unit_list(list); if (list->length != 0) { printf("Test failed - list length is %d, not 0\n",list->length); return 1; } // And try populating the list again, but a bit further this time for (ii=0; iilength > list->size) { printf("Test failed - list length = %d, size = %d\n", list->length,list->size); return 1; } else if (list->length != ii+1) { printf("Test failed - list length is %d, expected %d\n", list->length,ii+1); return 1; } free_ES_unit(&unit); } printf("Test 1 - clearing list\n"); free_ES_unit_list(&list); printf("Test 1 succeeded\n"); printf("Test 2 - the same ES unit inserted multiple times\n"); err = build_ES_unit_list(&list); if (err) { printf("Test failed - constructing list\n"); return 1; } err = build_ES_unit(&unit); if (err) { printf("Test failed - constructing ES unit\n"); return 1; } // We aren't testing allocation limits this time round for (ii=0; ii<5; ii++) { err = append_to_ES_unit_list(list,unit); if (err) { printf("Test failed - appending ES unit %d\n",ii); return 1; } } printf("Test 2 - clearing list\n"); free_ES_unit_list(&list); free_ES_unit(&unit); printf("Test 2 succeeded\n"); return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/test_nal_unit_list.c000066400000000000000000000106121261471605300207370ustar00rootroot00000000000000/* * A simple test for the NAL unit lists from nalunit.c * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include "compat.h" #include "nalunit_fns.h" int main(int argc, char **argv) { int err, ii; int max = NAL_UNIT_LIST_START_SIZE + NAL_UNIT_LIST_INCREMENT + 3; nal_unit_list_p list = NULL; nal_unit_p unit = NULL; printf("NAL unit lists are now handled as NAL unit lists, so...\n"); printf("Testing NAL unit list\n"); printf("Test 1 - differing NAL units\n"); err = build_nal_unit_list(&list); if (err) { printf("Test failed - constructing list\n"); return 1; } for (ii=0; iilength > list->size) { printf("Test failed - list length = %d, size = %d\n", list->length,list->size); return 1; } else if (list->length != ii+1) { printf("Test failed - list length is %d, expected %d\n", list->length,ii+1); return 1; } else if (list->array[ii] != unit) { printf("Test failed - list->array[%d] is %p, expected %p\n", ii,list->array[ii],unit); return 1; } } printf("Test 1 - resetting list\n"); reset_nal_unit_list(list,TRUE); if (list->length != 0) { printf("Test failed - list length is %d, not 0\n",list->length); return 1; } if (list->array[0] != NULL) { printf("Test failed - list->array[0] is %p, not NULL\n",list->array[0]); return 1; } // And try populating the list again, but a bit further this time for (ii=0; iilength > list->size) { printf("Test failed - list length = %d, size = %d\n", list->length,list->size); return 1; } else if (list->length != ii+1) { printf("Test failed - list length is %d, expected %d\n", list->length,ii+1); return 1; } else if (list->array[ii] != unit) { printf("Test failed - list->array[%d] is %p, expected %p\n", ii,list->array[ii],unit); return 1; } } printf("Test 1 - clearing list\n"); free_nal_unit_list(&list,TRUE); printf("Test 1 succeeded\n"); printf("Test 2 - the same NAL unit inserted multiple times\n"); err = build_nal_unit_list(&list); if (err) { printf("Test failed - constructing list\n"); return 1; } err = build_nal_unit(&unit); if (err) { printf("Test failed - constructing NAL unit\n"); return 1; } // We aren't testing allocation limits this time round for (ii=0; ii<5; ii++) { err = append_to_nal_unit_list(list,unit); if (err) { printf("Test failed - appending NAL unit %d\n",ii); return 1; } } printf("Test 2 - clearing list\n"); free_nal_unit_list(&list,FALSE); // better only do a shallow free free_nal_unit(&unit); printf("Test 2 succeeded\n"); return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/test_pes.c000066400000000000000000000315671261471605300166760ustar00rootroot00000000000000/* * Test the PES reading facilities * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include "compat.h" #include "pes_fns.h" #include "pidint_fns.h" #include "misc_fns.h" #include "ps_fns.h" #include "ts_fns.h" #include "es_fns.h" #include "h262_fns.h" #include "tswrite_fns.h" #include "version.h" /* * Write out TS program data based on the information we have */ static int write_program_data_A(PES_reader_p reader, TS_writer_p output) { // We know we support at most two program streams for output int num_progs = 0; uint32_t prog_pids[2]; byte prog_type[2]; int err; uint32_t pcr_pid, pmt_pid; if (reader->is_TS) { // For TS, we can use the stream types from the PMT itself int number; if (reader->video_pid != 0) { pmt_stream_p stream = pid_stream_in_pmt(reader->program_map, reader->video_pid); if (stream == NULL) { fprintf(stderr,"### Cannot find video PID %04x in program map\n", reader->video_pid); return 1; } prog_pids[0] = reader->output_video_pid; // may not be the same prog_type[0] = stream->stream_type; num_progs = 1; pcr_pid = reader->video_pid; } if (reader->audio_pid != 0) { pmt_stream_p stream = pid_stream_in_pmt(reader->program_map, reader->audio_pid); if (stream == NULL) { fprintf(stderr,"### Cannot find audio PID %04x in program map\n", reader->audio_pid); return 1; } prog_pids[num_progs] = reader->output_audio_pid; // may not be the same prog_type[num_progs] = stream->stream_type; num_progs++; } pmt_pid = reader->pmt_pid; } else { // For PS, we have to be given appropriate PIDs, and we need to // deduce stream types from the stream ids. Which, unfortunately, // we can't do. // For now, avoid the whole issue and just force some values... num_progs = 1; prog_pids[0] = 0x68; // hard-wired for video prog_type[0] = MPEG2_VIDEO_STREAM_TYPE; // hard-wired for now pcr_pid = 0x68; if (reader->audio_stream_id != 0) { prog_pids[1] = 0x67; // hard-wired again prog_type[1] = MPEG2_AUDIO_STREAM_TYPE; // a random guess num_progs = 2; } pmt_pid = 0x66; } err = write_TS_program_data2(output, 1, // transport stream id reader->program_number, pmt_pid,pcr_pid, num_progs,prog_pids,prog_type); if (err) { fprintf(stderr,"### Error writing out TS program data\n"); return 1; } return 0; } /* * Read PES packets and write them out to the target * * Returns 0 if all went well, 1 if an error occurred. */ static int play_pes_packets(PES_reader_p reader, TS_writer_p output) { int err; int ii; int pad_start = 8; int index = 0; ES_p es; // A view of our PES packets as ES units // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; iiunit.data, item->unit.data_len,DEFAULT_VIDEO_PID, DEFAULT_VIDEO_STREAM_ID); if (err) { fprintf(stderr,"### Error writing MPEG2 item\n"); return err; } free_h262_item(&item); } close_elementary_stream(&es); return 0; } static int test1(PES_reader_p reader, int verbose) { PES_packet_data_p packet; int ii; int err; byte *old_data; uint32_t old_data_len; if (verbose) printf("-------------------------- Test 1 --------------------------\n"); for (ii = 0; ii < 10; ii++) { err = read_next_PES_packet(reader); if (err == EOF) { if (reader->give_info) printf("EOF\n"); break; } else if (err) { fprintf(stderr,"### test_pes: Error reading next PES packet\n"); return 1; } packet = reader->packet; if (verbose) { printf("\n>> PS packet at " OFFSET_T_FORMAT " is %02x (", packet->posn,packet->data[3]); print_stream_id(TRUE,packet->data[3]); printf(")\n"); print_data(TRUE," Data",packet->data,packet->data_len,20); err = report_PES_data_array("",packet->data,packet->data_len,FALSE); if (err) return 1; } } err = read_next_PES_packet(reader); if (err) { fprintf(stderr,"### test_pes: Error reading next PES packet\n"); return 1; } packet = reader->packet; if (verbose) { printf("\n>> PS packet at " OFFSET_T_FORMAT " is %02x (", packet->posn,packet->data[3]); print_stream_id(TRUE,packet->data[3]); printf(")\n"); print_data(TRUE," Data",packet->data,packet->data_len,20); } old_data = malloc(packet->data_len); if (old_data == NULL) { fprintf(stderr,"### Error allocating data array\n"); return 1; } memcpy(old_data,packet->data,packet->data_len); old_data_len = packet->data_len; if (verbose) printf("\n** Rewinding to the start of said packet again\n"); err = set_PES_reader_position(reader,packet->posn); if (err) { fprintf(stderr,"### test_pes: Error seeking to previous PES packet\n"); free(old_data); return 1; } if (verbose) printf("** Reading packet the second time\n"); err = read_next_PES_packet(reader); if (err) { fprintf(stderr,"### test_pes: Error reading next PES packet\n"); free(old_data); return 1; } packet = reader->packet; if (verbose) { printf("\n>> PS packet at " OFFSET_T_FORMAT " is %02x (", packet->posn,packet->data[3]); print_stream_id(TRUE,packet->data[3]); printf(")\n"); print_data(TRUE," Data",packet->data,packet->data_len,20); } if (packet->data_len != old_data_len) { fprintf(stderr, "### Test1: first packet length %d, second packet length %d\n", old_data_len,packet->data_len); free(old_data); return 1; } else if (memcmp(packet->data,old_data,packet->data_len)) { fprintf(stderr,"### Test1: packet data differs\n"); print_data(FALSE," Packet 1",old_data,old_data_len,50); print_data(FALSE," Packet 2",packet->data,packet->data_len,50); free(old_data); return 1; } if (verbose) printf("------------------------------------------------------------\n"); // Even in a test it's a good idea to tidy up free(old_data); return 0; } static void print_usage() { printf( "Usage: test_pes [:]\n" "\n" ); REPORT_VERSION("test_pes"); printf( "\n" " Test the PES reading facilities. should be a TS\n" " (Transport Stream) or PS (Program Stream) file.\n" "\n" "Input:\n" " An H.222.0 TS or PS file.\n" " The host to which to write TS packets, over\n" " TCP/IP. If is not specified, it defaults\n" " to 88.\n" "\n" "Switches:\n" " -quiet, -q Suppress informational and warning messages.\n" " -verbose, -v Output additional diagnostic messages\n" " -noaudio Ignore any audio data\n" " -nohost Don't try to connect to the host\n" ); } int main(int argc, char **argv) { char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; int port = 88; // Useful default port number PES_reader_p reader = NULL; TS_writer_p output = NULL; int quiet = FALSE; int verbose = FALSE; int video_only = FALSE; int want_output = TRUE; int err; int ii = 1; if (argc < 2) { print_usage(); return 1; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { quiet = TRUE; verbose = FALSE; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-noaudio",argv[ii])) { video_only = TRUE; } else if (!strcmp("-nohost",argv[ii])) { want_output = FALSE; } else { fprintf(stderr,"### test_pes: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name && (want_output && had_output_name)) { fprintf(stderr,"### test_pes: Unexpected '%s'\n",argv[ii]); return 1; } else if (had_input_name && want_output) { err = host_value("test_pes",NULL,argv[ii],&output_name,&port); if (err) return 1; had_output_name = TRUE; // more or less ii++; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { fprintf(stderr,"### test_pes: No input file specified\n"); return 1; } if (want_output && !had_output_name) { fprintf(stderr,"### test_pes: No target host specified\n"); return 1; } err = open_PES_reader(input_name,!quiet,!quiet,&reader); if (err) { fprintf(stderr,"### test_pes: Error opening file %s\n",input_name); return 1; } if (!quiet) printf("Opened file %s (as %s)\n",input_name,(reader->is_TS?"TS":"PS")); set_PES_reader_video_only(reader,video_only); if (want_output) { err = tswrite_open(TS_W_TCP,output_name,NULL,port,quiet,&output); if (err) { (void) close_PES_reader(&reader); fprintf(stderr,"### test_pes: Unable to connect to %s\n",output_name); return 1; } } err = test1(reader,verbose); if (err) { fprintf(stderr,"### test_pes: Test 1 failed\n"); (void) close_PES_reader(&reader); (void) tswrite_close(output,TRUE); return 1; } if (!quiet) printf("** Test 1 passed\n" "** Rewinding\n"); err = set_PES_reader_position(reader,0); if (err) { fprintf(stderr,"### test_pes: Error seeking to previous PES packet\n"); return 1; } if (want_output) { err = play_pes_packets(reader,output); if (err) { fprintf(stderr,"### test_pes: Error playing PES packets\n"); (void) close_PES_reader(&reader); (void) tswrite_close(output,TRUE); return 1; } } if (want_output) { err = tswrite_close(output,quiet); if (err) fprintf(stderr,"### test_pes: Error closing output %s: %s\n",output_name, strerror(errno)); } err = close_PES_reader(&reader); if (err) { fprintf(stderr,"### test_pes: Error closing file %s\n",input_name); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/test_printing.c000066400000000000000000000122221261471605300177240ustar00rootroot00000000000000/* * Test the print redirection facilities * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include "printing_fns.h" #include "version.h" // Some example redirection routines static void print_message_to_stdout(const char *message) { (void) printf("<<>> %s",message); } static void print_message_to_stderr(const char *message) { (void) printf("<<>> %s",message); } static void fprint_message_to_stdout(const char *format, va_list arg_ptr) { printf("<<>> "); (void) vfprintf(stdout, format, arg_ptr); } static void fprint_message_to_stderr(const char *format, va_list arg_ptr) { printf("<<>> "); (void) vfprintf(stdout, format, arg_ptr); } static void print_usage() { printf( "Usage: test_printing\n" "\n" ); REPORT_VERSION("test_printing"); printf( "\n" " Test the print redirection facilities.\n" ); } int main(int argc, char **argv) { int err; if (argc > 1) { print_usage(); return 1; } printf("A fairly crude set of tests, mainly to check that nothing falls over.\n"); printf("For each set of tests, you should see 4 messages, all very similar.\n"); printf("------------------------------------\n"); printf("Testing the default output functions\n"); printf("------------------------------------\n"); print_msg("1. Printing a normal message\n"); print_err("2. Printing an error message\n"); fprint_msg("3. Printing a formatted '%s'\n","message"); fprint_err("4. Printing a formatted '%s'\n","error"); printf("-------------------------------------------\n"); printf("Choosing 'traditional' output and repeating\n"); printf("-------------------------------------------\n"); redirect_output_stderr(); print_msg("1. Printing a normal message\n"); print_err("2. Printing an error message\n"); fprint_msg("3. Printing a formatted '%s'\n","message"); fprint_err("4. Printing a formatted '%s'\n","error"); printf("---------------------------------------------\n"); printf("Choosing 'all output to stdout' and repeating\n"); printf("---------------------------------------------\n"); redirect_output_stdout(); print_msg("1. Printing a normal message\n"); print_err("2. Printing an error message\n"); fprint_msg("3. Printing a formatted '%s'\n","message"); fprint_err("4. Printing a formatted '%s'\n","error"); printf("-----------------------------------------\n"); printf("Choosing 'custom functions' and repeating\n"); printf("-----------------------------------------\n"); err = redirect_output(print_message_to_stdout, print_message_to_stderr, fprint_message_to_stdout, fprint_message_to_stderr); if (err) { printf("Oops -- that went wrong: %d\n",err); return 1; } print_msg("1. Printing a normal message\n"); print_err("2. Printing an error message\n"); fprint_msg("3. Printing a formatted '%s'\n","message"); fprint_err("4. Printing a formatted '%s'\n","error"); printf("---------------------------------------------\n"); printf("Trying to choose only *some* custom functions\n"); printf("---------------------------------------------\n"); err = redirect_output(print_message_to_stdout, print_message_to_stderr, NULL, fprint_message_to_stderr); if (err == 0) { printf("Oh dear, that appeared to work: %d\n",err); printf("So what happens if we try our tests again?\n"); print_msg("1. Printing a normal message\n"); print_err("2. Printing an error message\n"); fprint_msg("3. Printing a formatted '%s'\n","message"); fprint_err("4. Printing a formatted '%s'\n","error"); return 1; } printf("Which failed - good\n"); printf("------------------------------------------------------------------\n"); printf("After that (expected) failure, all four messages should still work\n"); printf("------------------------------------------------------------------\n"); print_msg("1. Printing a normal message\n"); print_err("2. Printing an error message\n"); fprint_msg("3. Printing a formatted '%s'\n","message"); fprint_err("4. Printing a formatted '%s'\n","error"); return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ts.c000066400000000000000000003744711261471605300155020ustar00rootroot00000000000000/* * Utilities for working with H.222 Transport Stream packets * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ts_fns.h" #include "tswrite_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "pidint_fns.h" #include "pes_fns.h" #define DEBUG 0 #define DEBUG_DTS 0 #define DEBUG_WRITE_PACKETS 0 // Should we report reserved bits that are set to the wrong value? // For the moment, make this a global, since it lets me suppress // it easily. There should be some sort of command line switch // to set this to FALSE in utilities that it matters for. static int report_bad_reserved_bits = FALSE; // ============================================================ // Suppport for the creation of Transport Streams. // ============================================================ // Remember the continuity counter value for each/any PID // (do I really want to have an array of size 8191+1? // and do I want it static? not if this ever becomes a // library module...) static int continuity_counter[0x1fff+1] = {0}; /* * Return the next value of continuity_counter for the given pid */ static inline int next_continuity_count(uint32_t pid) { uint32_t next = (continuity_counter[pid] + 1) & 0x0f; continuity_counter[pid] = next; return next; } /* * Create a PES header for our data. * * - `data_len` is the length of our ES data * If this is too long to fit into 16 bits, then we will create a header * with 0 as its length. Note this is only allowed (by the standard) for * video data. * - `stream_id` is the elementary stream id to use (see H.222 Table 2-18). * If the stream id indicates an audio stream (as elucidated by Note 2 in * that same table), then the data_alignment_indicator flag will be set * in the PES header - i.e., we assume that the audio frame *starts* * (has its syncword) at the start of the PES packet payload. * - `with_PTS` should be TRUE if the PTS value in `pts` should be written * to the PES header. * - `with_DTS` should be TRUE if the DTS value in `dts` should be written * to the PES header. Note that if `with_DTS` is TRUE, then `with_PTS` * must also be TRUE. If it is not, then the DTS value will be used for * the PTS. * - `PES_hdr` is the resultant PES packet header, and * - `PES_hdr_len` its length (at the moment that's always the same, as * we're not yet outputting any timing information (PTS/DTS), and so * can get away with a minimal PES header). */ extern void PES_header(uint32_t data_len, byte stream_id, int with_PTS, uint64_t pts, int with_DTS, uint64_t dts, byte *PES_hdr, int *PES_hdr_len) { int extra_len = 0; if (with_DTS && !with_PTS) { with_PTS = TRUE; pts = dts; } // If PTS=DTS then there is no point explictly coding the DTS so junk it if (with_DTS && pts == dts) with_DTS = FALSE; // packet_start_code_prefix PES_hdr[0] = 0x00; PES_hdr[1] = 0x00; PES_hdr[2] = 0x01; PES_hdr[3] = stream_id; // PES_packet_length comes next, but we'll actually sort it out // at the end, when we know what else we've put into our header // Flags: '10' then PES_scrambling_control .. original_or_copy // If it appears to be an audio stream, we set the data alignment indicator // flag, to indicate that the audio data starts with its syncword. For video // data, we leave the flag unset. if (IS_AUDIO_STREAM_ID(stream_id)) PES_hdr[6] = 0x84; // just data alignment indicator flag set else PES_hdr[6] = 0x80; // no flags set // Flags: PTS_DTS_flags .. PES_extension_flag if (with_DTS && with_PTS) PES_hdr[7] = 0xC0; else if (with_PTS) PES_hdr[7] = 0x80; else PES_hdr[7] = 0x00; // yet more unset flags (nb: no PTS/DTS info) // PES_header_data_length if (with_DTS && with_PTS) { PES_hdr[8] = 0x0A; encode_pts_dts(&(PES_hdr[9]),3,pts); encode_pts_dts(&(PES_hdr[14]),1,dts); *PES_hdr_len = 9 + 10; extra_len = 3 + 10; // 3 bytes after the length field, plus our PTS & DTS } else if (with_PTS) { PES_hdr[8] = 0x05; encode_pts_dts(&(PES_hdr[9]),2,pts); *PES_hdr_len = 9 + 5; extra_len = 3 + 5; // 3 bytes after the length field, plus our PTS } else { PES_hdr[8] = 0x00; // 0 means there is no more data *PES_hdr_len = 9; extra_len = 3; // just the basic 3 bytes after the length field } // So now we can set the length field itself... if (data_len > 0xFFFF || (data_len + extra_len) > 0xFFFF) { // If the length is too great, we just set it "unset" // @@@ (this should only really be done for TS-wrapped video, so perhaps // we should complain if this is not video?) PES_hdr[4] = 0; PES_hdr[5] = 0; } else { // The packet length doesn't include the bytes up to and including the // packet length field, but it *does* include any bytes of the PES header // after it. data_len += extra_len; PES_hdr[4] = (byte) ((data_len & 0xFF00) >> 8); PES_hdr[5] = (byte) ((data_len & 0x00FF)); } } /* * Write out our TS packet, as composed from its parts. * * - `output` is the TS writer context to write with * - `TS_packet` is the TS packet buffer, already filled to length `TS_hdr_len` * with TS header information * - `pes_hdr` is the PES header data, length `pes_hdr_len`, which is * written out thereafter * - `data` is then the actual ES data, length `data_len` * * When outputting asynchronously, the writer needs to know any timing * information that is available for the packet. Thus: * * - `pid` is the PID for the packet * - `got_pcr` is TRUE if we have a PCR for the packet, in which case * - `pcr` is that PCR. * * Restrictions * ============ * * `TS_hdr_len` + `pes_hdr_len` + `data_len` should equal 188. * * `pes_hdr_len` may be 0 if there is no PES header data to be written * (i.e., this is not the start of the PES packet's data, or the packet * is not a PES packet). * * For real data, `data_len` should never be 0 (the exception is when * writing NULL packets). * * `TS_hdr_len` must never be 0. * * Returns 0 if it worked, 1 if something went wrong. */ static int write_TS_packet_parts(TS_writer_p output, byte TS_packet[TS_PACKET_SIZE], int TS_hdr_len, byte pes_hdr[], int pes_hdr_len, byte data[], int data_len, uint32_t pid, int got_pcr, uint64_t pcr) { int err; int total_len = TS_hdr_len + pes_hdr_len + data_len; if (total_len != TS_PACKET_SIZE) { fprint_err("### TS packet length is %d, not 188 (composed of %d + %d + %d)\n", total_len,TS_hdr_len,pes_hdr_len,data_len); return 1; } // We want to make a single write, so we need to assemble the package // into our packet buffer if (pes_hdr_len > 0) memcpy(&(TS_packet[TS_hdr_len]),pes_hdr,pes_hdr_len); if (data_len > 0) memcpy(&(TS_packet[TS_hdr_len+pes_hdr_len]),data,data_len); err = tswrite_write(output,TS_packet,pid,got_pcr,pcr); if (err) { fprint_err("### Error writing out TS packet: %s\n",strerror(errno)); return 1; } return 0; } /* * Write our data as a (series of) Transport Stream PES packets. * * - `output` is the TS writer context we're using to write our TS data out * - `pes_hdr` is NULL if the data to be written out is already PES, and is * otherwise a PES header constructed with PES_header() * - `pes_hdr_len` is the length of said PES header (or 0) * - `data` is (the remainder of) our ES data (e.g., a NAL unit) or PES packet * - `data_len` is its length * - `start` is true if this is the first time we've called this function * to output (part of) this data (in other words, this should be true * when someone else calls this function, and false when the function * calls itself). This is expected to be TRUE if a PES header is given... * - `set_pusi` is TRUE if we should set the payload unit start indicator * (generally true if `start` is TRUE). This is ignored if `start` is FALSE. * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * - `got_PCR` is TRUE if we have a `PCR` value (this is only * relevant when `start` is also TRUE). * - `PCR_base` and `PCR_extn` then encode that PCR value (ditto) * * Returns 0 if it worked, 1 if something went wrong. */ static int write_some_TS_PES_packet(TS_writer_p output, byte *pes_hdr, int pes_hdr_len, byte *data, uint32_t data_len, int start, int set_pusi, uint32_t pid, byte stream_id, int got_PCR, uint64_t PCR_base, uint32_t PCR_extn) { #define DEBUG_THIS 0 byte TS_packet[TS_PACKET_SIZE]; int TS_hdr_len; uint32_t controls = 0; uint32_t pes_data_len = 0; int err; int got_adaptation_field = FALSE; uint32_t space_left; // Bytes available for payload, after the TS header if (pid < 0x0010 || pid > 0x1ffe) { fprint_err("### PID %03x is outside legal program stream range",pid); return 1; } // If this is the first time we've "seen" this data, and it is not // already wrapped up as PES, then we need to remember its PES header // in our calculations if (pes_hdr) pes_data_len = data_len + pes_hdr_len; else { pes_hdr_len = 0; pes_data_len = data_len; } #if DEBUG_THIS if (start) print_msg("TS_PES "); else print_msg(" "); print_data(TRUE,"",data,data_len,20); #endif // We always start with a sync_byte to identify this as a // Transport Stream packet TS_packet[0] = 0x47; // Should we set the "payload_unit_start_indicator" bit? // Only for the first packet containing our data. if (start && set_pusi) TS_packet[1] = (byte)(0x40 | ((pid & 0x1f00) >> 8)); else TS_packet[1] = (byte)(0x00 | ((pid & 0x1f00) >> 8)); TS_packet[2] = (byte)(pid & 0xff); // Sort out the adaptation field, if any if (start && got_PCR) { // This is the start of the data, and we have a PCR value to output, // so we know we have an adaptation field controls = 0x30; // adaptation field control = '11' = both TS_packet[3] = (byte) (controls | next_continuity_count(pid)); // And construct said adaptation field... TS_packet[4] = 7; // initial adaptation field length TS_packet[5] = 0x10; // flag bits 0001 0000 -> got PCR TS_packet[6] = (byte) (PCR_base >> 25); TS_packet[7] = (byte) ((PCR_base >> 17) & 0xFF); TS_packet[8] = (byte) ((PCR_base >> 9) & 0xFF); TS_packet[9] = (byte) ((PCR_base >> 1) & 0xFF); TS_packet[10] = (byte) (((PCR_base & 0x1) << 7) | 0x7E | (PCR_extn >> 8)); TS_packet[11] = (byte) (PCR_extn >> 1); TS_hdr_len = 12; space_left = MAX_TS_PAYLOAD_SIZE - 8; got_adaptation_field = TRUE; #if DEBUG_THIS fprint_msg(" start & got_PCR -> with adaptation field, space left %d, TS_packet[4] %d\n",space_left,TS_packet[4]); #endif } else if (pes_data_len < MAX_TS_PAYLOAD_SIZE) { // Our data is less than 184 bytes long, which means it won't fill // the payload, so we need to pad it out with an (empty) adaptation // field, padded to the appropriate length controls = 0x30; // adaptation field control = '11' = both TS_packet[3] = (byte)(controls | next_continuity_count(pid)); if (pes_data_len == (MAX_TS_PAYLOAD_SIZE - 1)) // i.e., 183 { TS_packet[4] = 0; // just the length used to pad TS_hdr_len = 5; space_left = MAX_TS_PAYLOAD_SIZE - 1; } else { TS_packet[4] = 1; // initial length TS_packet[5] = 0; // unset flag bits TS_hdr_len = 6; space_left = MAX_TS_PAYLOAD_SIZE - 2; // i.e., 182 } got_adaptation_field = TRUE; #if DEBUG_THIS fprint_msg(" <184, pad with empty adaptation field, space left %d, TS_packet[4] %d\n",space_left,TS_packet[4]); #endif } else { // The data either fits exactly, or is too long and will need to be // continued in further TS packets. In either case, we don't need an // adaptation field controls = 0x10; // adaptation field control = '01' = payload only TS_packet[3] = (byte)(controls | next_continuity_count(pid)); TS_hdr_len = 4; space_left = MAX_TS_PAYLOAD_SIZE; #if DEBUG_THIS fprint_msg(" >=184, space left %d\n",space_left); #endif } if (got_adaptation_field) { // Do we need to add stuffing bytes to allow for short PES data? if (pes_data_len < space_left) { int ii; int padlen = space_left - pes_data_len; for (ii = 0; ii < padlen; ii++) TS_packet[TS_hdr_len+ii] = 0xFF; TS_packet[4] += padlen; TS_hdr_len += padlen; space_left -= padlen; #if DEBUG_THIS fprint_msg(" stuffing %d, space left %d, TS_packet[4] %d\n",padlen,space_left,TS_packet[4]); #endif } } if (pes_data_len == space_left) { #if DEBUG_THIS print_msg(" == fits exactly\n"); #endif // Our data fits exactly err = write_TS_packet_parts(output, TS_packet,TS_hdr_len, pes_hdr,pes_hdr_len, data,data_len, pid,got_PCR,(PCR_base*300)+PCR_extn); if (err) return err; } else { // We need to look at more than one packet... // Write out the first 184-pes_hdr_len bytes int increment = space_left - pes_hdr_len; err = write_TS_packet_parts(output, TS_packet,TS_hdr_len, pes_hdr,pes_hdr_len, data,increment, pid,got_PCR,(PCR_base*300)+PCR_extn); if (err) return err; #if DEBUG_THIS fprint_msg(" == wrote %d, leaving %d\n",increment,data_len-increment); #endif // Leaving data_len - (184-pes_hdr_len) bytes still to go // Is recursion going to be efficient enough? if ((data_len - increment) > 0) { err = write_some_TS_PES_packet(output,NULL,0, &(data[increment]),data_len-increment, FALSE,FALSE,pid,stream_id,FALSE,0,0); if (err) return err; } } return 0; } /* * Write out our ES data as a Transport Stream PES packet. * * - `output` is the TS output context returned by `tswrite_open` * - `data` is our ES data (e.g., a NAL unit) * - `data_len` is its length * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * * If the data to be written is more than 65535 bytes long (i.e., the * length will not fit into 2 bytes), then the PES packet written will * have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0) * 2.4.3.7, Semantic definitions of fields in PES packet). This is only * allowed for video streams. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_ES_as_TS_PES_packet(TS_writer_p output, byte data[], uint32_t data_len, uint32_t pid, byte stream_id) { byte pes_hdr[TS_PACKET_SIZE]; // better be more than long enough! int pes_hdr_len = 0; #if DEBUG_WRITE_PACKETS fprint_msg("|| ES as TS/PES, pid %x (%d)\n",pid,pid); #endif PES_header(data_len,stream_id,FALSE,0,FALSE,0,pes_hdr,&pes_hdr_len); return write_some_TS_PES_packet(output,pes_hdr,pes_hdr_len, data,data_len,TRUE,TRUE,pid,stream_id, FALSE,0,0); } /* * Write out our ES data as a Transport Stream PES packet, with PTS and/or DTS * if we've got them, and some attempt to write out a sensible PCR. * * - `output` is the TS output context returned by `tswrite_open` * - `data` is our ES data (e.g., a NAL unit) * - `data_len` is its length * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * - `got_pts` is TRUE if we have a PTS value, in which case * - `pts` is said PTS value * - `got_dts` is TRUE if we also have DTS, in which case * - `dts` is said DTS value. * * We also want to try to write out a sensible PCR value. * * PTS can go up as well as down (it is the time at which the next frame * should be presented to the user, but frames do not necessarily occur * in presentation order). * * DTS only goes up, since it is the time that the frame should be decoded. * * Thus, if we have it, the DTS is sensible to use for the PCR... * * If the data to be written is more than 65535 bytes long (i.e., the * length will not fit into 2 bytes), then the PES packet written will * have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0) * 2.4.3.7, Semantic definitions of fields in PES packet). This is only * allowed for video streams. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_ES_as_TS_PES_packet_with_pts_dts(TS_writer_p output, byte data[], uint32_t data_len, uint32_t pid, byte stream_id, int got_pts, uint64_t pts, int got_dts, uint64_t dts) { byte pes_hdr[TS_PACKET_SIZE]; // better be more than long enough! int pes_hdr_len = 0; #if DEBUG_WRITE_PACKETS fprint_msg("|| ES as TS/PES with PTS/DTS, pid %x (%d)\n",pid,pid); #endif PES_header(data_len,stream_id,got_pts,pts,got_dts,dts,pes_hdr,&pes_hdr_len); return write_some_TS_PES_packet(output,pes_hdr,pes_hdr_len, data,data_len,TRUE,TRUE,pid,stream_id, got_dts,dts,0); } /* * Write out our ES data as a Transport Stream PES packet, with PCR. * * - `output` is the TS output context returned by `tswrite_open` * - `data` is our ES data (e.g., a NAL unit) * - `data_len` is its length * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * - `pcr_base` and `pcr_extn` encode the PCR value. * * If the data to be written is more than 65535 bytes long (i.e., the * length will not fit into 2 bytes), then the PES packet written will * have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0) * 2.4.3.7, Semantic definitions of fields in PES packet). This is only * allowed for video streams. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_ES_as_TS_PES_packet_with_pcr(TS_writer_p output, byte data[], uint32_t data_len, uint32_t pid, byte stream_id, uint64_t pcr_base, uint32_t pcr_extn) { byte pes_hdr[TS_PACKET_SIZE]; // better be more than long enough! int pes_hdr_len = 0; #if DEBUG_WRITE_PACKETS fprint_msg("|| ES as TS/PES with PCR, pid %x (%d)\n",pid,pid); #endif PES_header(data_len,stream_id,FALSE,0,FALSE,0,pes_hdr,&pes_hdr_len); return write_some_TS_PES_packet(output,pes_hdr,pes_hdr_len, data,data_len,TRUE,TRUE,pid,stream_id, TRUE,pcr_base,pcr_extn); } /* * Write out a PES packet's data as a Transport Stream PES packet. * * - `output` is the TS output context returned by `tswrite_open` * - `data` is our PES data (e.g., a program stream video data packet) * - `data_len` is its length * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * - `got_pcr` is TRUE if we have values for the PCR in this packet, * in which case `pcr_base` and `pcr_extn` are the parts of the PCR. * * If the data to be written is more than 65535 bytes long (i.e., the * length will not fit into 2 bytes), then the PES packet written will * have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0) * 2.4.3.7, Semantic definitions of fields in PES packet). This is only * allowed for video streams. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_PES_as_TS_PES_packet(TS_writer_p output, byte data[], uint32_t data_len, uint32_t pid, byte stream_id, int got_pcr, uint64_t pcr_base, uint32_t pcr_extn) { // Should we write MPEG-1 packet data out as ES (wrapped in MPEG-2 PES in TS), // rather than writing the packets out directly in TS? (that latter doesn't // work very well, as TS is not defined to work for MPEG-1 style packets). #define MPEG1_AS_ES 1 #if DEBUG_WRITE_PACKETS fprint_msg("|| PES as TS/PES, pid %x (%d)\n",pid,pid); #endif #if 0 // XXX print_data(TRUE,"TS_PES",data,data_len,20); print_end_of_data(" ",data,data_len,20); #endif // XXX #if MPEG1_AS_ES if (IS_H222_PES(data)) { #endif // MPEG1_AS_ES return write_some_TS_PES_packet(output,NULL,0, data,data_len,TRUE,TRUE,pid,stream_id, got_pcr,pcr_base,pcr_extn); #if MPEG1_AS_ES } else { // Write MPEG-1 data out as ES in (MPEG-2) PES int got_pts, got_dts; uint64_t pts, dts; int offset = calc_mpeg1_pes_offset(data,data_len); int err = find_PTS_DTS_in_PES(data,data_len, &got_pts,&pts,&got_dts,&dts); if (err) // Just try to carry on... { got_pts = FALSE; got_dts = FALSE; } return write_ES_as_TS_PES_packet_with_pts_dts(output, data + offset, data_len - offset, pid, stream_id, got_pts,pts, got_dts,dts); } #endif // MPEG1_AS_ES } /* * Construct a Transport Stream packet header for PAT or PMT data. * * The data is required to fit within a single TS packet - i.e., to be * 183 bytes or less. * * - `pid` is the PID to use for this packet. * - `data_len` is the length of the PAT or PMT data * - `TS_hdr` is a byte array into (the start of) which to write the * TS header data. * - `TS_hdr_len` returns how much data we've written therein. * * Returns 0 if it worked, 1 if something went wrong. */ static int TS_program_packet_hdr(uint32_t pid, int data_len, byte TS_hdr[TS_PACKET_SIZE], int *TS_hdr_len) { uint32_t controls = 0; int pointer, ii; if (data_len > (TS_PACKET_SIZE - 5)) // i.e., 183 { fprint_err("### PMT/PAT data for PID %02x is too long (%d > 183)", pid,data_len); return 1; } // We always start with a sync_byte to identify this as a // Transport Stream packet TS_hdr[0] = 0x47; // We want the "payload_unit_start_indicator" bit set TS_hdr[1] = (byte)(0x40 | ((pid & 0x1f00) >> 8)); TS_hdr[2] = (byte)(pid & 0xff); // We don't need any adaptation field controls controls = 0x10; TS_hdr[3] = (byte)(controls | next_continuity_count(pid)); // Next comes a pointer to the actual payload data // (i.e., 0 if the data is 183 bytes long) // followed by pad bytes until we *get* to the data pointer = (byte)(TS_PACKET_SIZE - 5 - data_len); TS_hdr[4] = pointer; for (ii=0; iilength * 4; if (section_length > 1021) { print_err("### PAT data is too long - will not fit in 1021 bytes\n"); // TODO: Ideally, would be to stderr report_pidint_list(prog_list,"Program list","Program",FALSE); return 1; } data[0] = 0x00; // The section length is fixed because our data is fixed data[1] = (byte) (0xb0 | ((section_length & 0x0F00) >> 8)); data[2] = (byte) (section_length & 0x0FF); data[3] = (byte) ((transport_stream_id & 0xFF00) >> 8); data[4] = (byte) (transport_stream_id & 0x00FF); // For simplicity, we'll have a version_id of 0 data[5] = 0xc1; // First section of the PAT has section number 0, and there is only // that section data[6] = 0x00; data[7] = 0x00; offset = 8; for (ii = 0; ii < prog_list->length; ii++) { data[offset+0] = (byte) ((prog_list->number[ii] & 0xFF00) >> 8); data[offset+1] = (byte) (prog_list->number[ii] & 0x00FF); data[offset+2] = (byte) (0xE0 | ((prog_list->pid[ii] & 0x1F00) >> 8)); data[offset+3] = (byte) (prog_list->pid[ii] & 0x00FF); offset += 4; } crc32 = crc32_block(0xffffffff,data,offset); data[12] = (byte) ((crc32 & 0xff000000) >> 24); data[13] = (byte) ((crc32 & 0x00ff0000) >> 16); data[14] = (byte) ((crc32 & 0x0000ff00) >> 8); data[15] = (byte) (crc32 & 0x000000ff); data_length = offset+4; #if 1 if (data_length != section_length + 3) { fprint_err("### PAT length %d, section length+3 %d\n", data_length,section_length+3); return 1; } #endif crc32 = crc32_block(0xffffffff,data,data_length); if (crc32 != 0) { print_err("### PAT CRC does not self-cancel\n"); return 1; } err = TS_program_packet_hdr(0x00,data_length,TS_packet,&TS_hdr_len); if (err) { print_err("### Error constructing PAT packet header\n"); return 1; } err = write_TS_packet_parts(output,TS_packet,TS_hdr_len,NULL,0, data,data_length,0x00,FALSE,0); if (err) { print_err("### Error writing PAT\n"); return 1; } return 0; } /* * Write out a Transport Stream PMT, given a PMT datastructure * * - `output` is the TS output context returned by `tswrite_open` * - `pmt_pid` is the PID for the PMT. * - 'pmt' is the datastructure containing the PMT information * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_pmt(TS_writer_p output, uint32_t pmt_pid, pmt_p pmt) { int ii; byte data[3+1021]; // maximum PMT size byte TS_packet[TS_PACKET_SIZE]; int TS_hdr_len; int err; int section_length; int offset, data_length; uint32_t crc32; #if DEBUG_WRITE_PACKETS fprint_msg("|| PMT pid %x (%d)\n",pmt_pid,pmt_pid); #endif if (pmt_pid < 0x0010 || pmt_pid > 0x1ffe) { fprint_err("### PMT PID %03x is outside legal range\n",pmt_pid); return 1; } if (pid_in_pmt(pmt,pmt_pid)) { fprint_err("### PMT PID and program %d PID are both %03x\n", pid_index_in_pmt(pmt,pmt_pid),pmt_pid); return 1; } // Much of the PMT should look very familiar, after the PAT // Calculate the length of the section section_length = 13 + pmt->program_info_length; for (ii = 0; ii < pmt->num_streams; ii++) section_length += 5 + pmt->streams[ii].ES_info_length; if (section_length > 1021) { print_err("### PMT data is too long - will not fit in 1021 bytes\n"); report_pmt(FALSE," ",pmt); return 1; } data[0] = 0x02; data[1] = (byte) (0xb0 | ((section_length & 0x0F00) >> 8)); data[2] = (byte) (section_length & 0x0FF); data[3] = (byte) ((pmt->program_number & 0xFF00) >> 8); data[4] = (byte) (pmt->program_number & 0x00FF); data[5] = 0xc1; data[6] = 0x00; // section number data[7] = 0x00; // last section number data[8] = (byte) (0xE0 | ((pmt->PCR_pid & 0x1F00) >> 8)); data[9] = (byte) (pmt->PCR_pid & 0x00FF); data[10] = 0xF0; data[11] = (byte)pmt->program_info_length; if (pmt->program_info_length > 0) memcpy(data+12,pmt->program_info,pmt->program_info_length); offset = 12 + pmt->program_info_length; for (ii=0; ii < pmt->num_streams; ii++) { uint32_t pid = pmt->streams[ii].elementary_PID; uint16_t len = pmt->streams[ii].ES_info_length; data[offset+0] = pmt->streams[ii].stream_type; data[offset+1] = (byte) (0xE0 | ((pid & 0x1F00) >> 8)); data[offset+2] = (byte) (pid & 0x00FF); data[offset+3] = ((len & 0xFF00) >> 8) | 0xF0; data[offset+4] = len & 0x00FF; memcpy(data+offset+5,pmt->streams[ii].ES_info,len); offset += 5 + len; } crc32 = crc32_block(0xffffffff,data,offset); data[offset+0] = (byte) ((crc32 & 0xff000000) >> 24); data[offset+1] = (byte) ((crc32 & 0x00ff0000) >> 16); data[offset+2] = (byte) ((crc32 & 0x0000ff00) >> 8); data[offset+3] = (byte) (crc32 & 0x000000ff); data_length = offset + 4; #if 1 if (data_length != section_length + 3) { fprint_err("### PMT length %d, section length+3 %d\n", data_length,section_length+3); return 1; } #endif crc32 = crc32_block(0xffffffff,data,data_length); if (crc32 != 0) { print_err("### PMT CRC does not self-cancel\n"); return 1; } err = TS_program_packet_hdr(pmt_pid,data_length,TS_packet,&TS_hdr_len); if (err) { print_err("### Error constructing PMT packet header\n"); return 1; } err = write_TS_packet_parts(output,TS_packet,TS_hdr_len,NULL,0, data,data_length,0x02,FALSE,0); if (err) { print_err("### Error writing PMT\n"); return 1; } return 0; } /* * Write out a Transport Stream PAT and PMT, given the appropriate * datastructures * * - `output` is the TS output context returned by `tswrite_open` * - `transport_stream_id` is the id for this particular transport stream. * - `prog_list` is a PIDINT list of program number / PID pairs. * - `pmt_pid` is the PID for the PMT. * - 'pmt' is the datastructure containing the PMT information * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_pat_and_pmt(TS_writer_p output, uint32_t transport_stream_id, pidint_list_p prog_list, uint32_t pmt_pid, pmt_p pmt) { int err; err = write_pat(output,transport_stream_id,prog_list); if (err) return 1; err = write_pmt(output,pmt_pid,pmt); if (err) return 1; return 0; } /* * Write out a Transport Stream PAT, for a single program. * * - `output` is the TS output context returned by `tswrite_open` * - `transport_stream_id` is the id for this particular transport stream. * - `program_number` is the program number to use for the PID. * - `pmt_pid` is the PID for the PMT. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_single_program_pat(TS_writer_p output, uint32_t transport_stream_id, uint32_t program_number, uint32_t pmt_pid) { int err; pidint_list_p prog_list; err = build_pidint_list(&prog_list); if (err) return 1; err = append_to_pidint_list(prog_list,pmt_pid,program_number); if (err) { free_pidint_list(&prog_list); return 1; } err = write_pat(output,transport_stream_id,prog_list); if (err) { free_pidint_list(&prog_list); return 1; } free_pidint_list(&prog_list); return 0; } /* * Write out a Transport Stream Null packet. * * - `output` is the TS output context returned by `tswrite_open` * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_TS_null_packet(TS_writer_p output) { byte TS_packet[TS_PACKET_SIZE]; int err, ii; #if DEBUG_WRITE_PACKETS print_msg("|| Null packet\n"); #endif TS_packet[0] = 0x47; TS_packet[1] = 0x1F; // PID is 0x1FFF TS_packet[2] = 0xFF; TS_packet[3] = 0x20; // payload only for (ii=4; iifile = -1; *tsreader = new; return 0; } // ------------------------------------------------------------ // File handling // ------------------------------------------------------------ /* * Build a TS packet reader, including its read-ahead buffer * * - `file` is the file that the TS packets will be read from. * It is assumed that its read position is at its start. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_TS_reader(int file, TS_reader_p *tsreader) { TS_reader_p new; int err = new_TS_reader(&new); if (err) return 1; new->file = file; *tsreader = new; return 0; } /* * Build a TS packet reader using the given functions as read() and seek(). * * Returns 0 on success, 1 on failure. */ extern int build_TS_reader_with_fns(void *handle, int (*read_fn)(void *, byte *, size_t), int (*seek_fn)(void *, offset_t), TS_reader_p *tsreader) { TS_reader_p new; int err = new_TS_reader(&new); if (err) return 1; new->handle = handle; new->read_fn = read_fn; new->seek_fn = seek_fn; *tsreader = new; return 0; } /* * Open a file to read TS packets from. * * If `filename` is NULL, then the input will be taken from standard input. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int open_file_for_TS_read(char *filename, TS_reader_p *tsreader) { int err; int file; if (filename == NULL) file = STDIN_FILENO; else { file = open_binary_file(filename,FALSE); if (file == -1) return 1; } err = build_TS_reader(file,tsreader); if (err) { (void) close_file(file); return 1; } return 0; } /* * Free a TS packet read-ahead buffer * * Sets `buffer` to NULL. */ extern void free_TS_reader(TS_reader_p *tsreader) { if (*tsreader != NULL) { if ((*tsreader)->pcrbuf != NULL) free((*tsreader)->pcrbuf); (*tsreader)->file = -1; free(*tsreader); *tsreader = NULL; } } /* * Free a TS packet read-ahead buffer and close the referenced file * (if it is not standard input). * * Sets `buffer` to NULL, whether the file close succeeds or not. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int close_TS_reader(TS_reader_p *tsreader) { int err = 0; if (*tsreader == NULL) return 0; if ((*tsreader)->file != STDIN_FILENO && (*tsreader)->file != -1) err = close_file((*tsreader)->file); free_TS_reader(tsreader); return err; } /* * Seek to a given offset in the TS reader's file * * (This should be used in preference to just seeking on the "bare" file * since it also unsets the read-ahead buffer. However, it is still just * a wrapper around `seek_file`.) * * It is assumed (but not checked) that the seek will end up at an appropriate * offset for reading a TS packet - i.e., presumably some multiple of * TS_PACKET_SIZE. * * Returns 0 if all goes well, 1 if something goes wrong */ extern int seek_using_TS_reader(TS_reader_p tsreader, offset_t posn) { tsreader->read_ahead_ptr = NULL; tsreader->read_ahead_end = NULL; tsreader->posn = posn; if (tsreader->seek_fn) { return tsreader->seek_fn(tsreader->handle, posn); } else { return seek_file(tsreader->file,posn); } } /* * Read the next several TS packets, possibly not from the start * * - `tsreader` is the TS packet reading context * - `start_len` is the number of bytes of the first packet we've already * got in hand - normally 0. * - `packet` is (a pointer to) the resultant TS packet. * * This is a pointer into the reader's read-ahead buffer, and so should not * be freed. Note that this means that it may not persist after another call * of this function (and will not persist after a call of * `free_TS_reader`). * * Returns 0 if all goes well, EOF if end of file was read, or 1 if some * other error occurred (in which case it will already have output a message * on stderr about the problem). */ static int read_next_TS_packets(TS_reader_p tsreader, int start_len, byte *packet[TS_PACKET_SIZE]) { #ifdef _WIN32 int total = start_len; int length; #else ssize_t total = start_len; ssize_t length; #endif // If we exit with an error make sure we don't return anything valid here! *packet = NULL; if (tsreader->read_ahead_ptr == tsreader->read_ahead_end) { // Try to allow for partial reads while (total < TS_READ_AHEAD_BYTES) { if (tsreader->read_fn) length = tsreader->read_fn(tsreader->handle, &(tsreader->read_ahead[total]), TS_READ_AHEAD_BYTES-total); else length = read(tsreader->file, &(tsreader->read_ahead[total]), TS_READ_AHEAD_BYTES - total); if (length == 0) // EOF - no more data to read break; else if (length == -1) { fprint_err("### Error reading TS packets: %s\n",strerror(errno)); return 1; } total += length; } // If we didn't manage to read anything at all, then indicate EOF this // time - we assume that if we actually read to the EOF but got some data, // we'll "hit" EOF again next time we try to read. if (total == 0) return EOF; if (total % TS_PACKET_SIZE != 0) { fprint_err("!!! %d byte%s ignored at end of file - not enough" " to make a TS packet\n", (int)(total % TS_PACKET_SIZE),(total % TS_PACKET_SIZE == 1?"":"s")); // Retain whatever full packets we *do* have total = total - (total % TS_PACKET_SIZE); if (total == 0) return EOF; } tsreader->read_ahead_ptr = tsreader->read_ahead; tsreader->read_ahead_end = tsreader->read_ahead + total; } *packet = tsreader->read_ahead_ptr; tsreader->read_ahead_ptr += TS_PACKET_SIZE; // ready for next time tsreader->posn += TS_PACKET_SIZE; // ditto return 0; } /* * Read the (rest of the) first TS packet, given its first four bytes * * This is intended for use after inspecting the first four bytes of the * input file, to determine if the file is TS or PS. * * - `tsreader` is the TS packet reading context * - `start` is the first four bytes of the file * - `packet` is (a pointer to) the resultant TS packet. * * This is a pointer into the reader's read-ahead buffer, and so should not * be freed. Note that this means that it may not persist after another call * of this function (and will not persist after a call of * `free_TS_reader`). * * Note that the caller is trusted to call this only when appropriate. * * Returns 0 if all goes well, EOF if end of file was read, or 1 if some * other error occurred (in which case it will already have output a message * on stderr about the problem). */ extern int read_rest_of_first_TS_packet(TS_reader_p tsreader, byte start[4], byte **packet) { tsreader->read_ahead[0] = start[0]; tsreader->read_ahead[1] = start[1]; tsreader->read_ahead[2] = start[2]; tsreader->read_ahead[3] = start[3]; // So we already have the first 4 bytes in hand return read_next_TS_packets(tsreader,4,packet); } /* * Read the next TS packet. * * - `tsreader` is the TS packet reading context * - `packet` is (a pointer to) the resultant TS packet. * * This is a pointer into the reader's read-ahead buffer, and so should not * be freed. Note that this means that it may not persist after another call * of this function (and will not persist after a call of * `free_TS_reader`). * * Returns 0 if all goes well, EOF if end of file was read, or 1 if some * other error occurred (in which case it will already have output a message * on stderr about the problem). */ extern int read_next_TS_packet(TS_reader_p tsreader, byte **packet) { return read_next_TS_packets(tsreader,0,packet); } // ------------------------------------------------------------ // Reading a transport stream with buffered timing // Keeps a PCR in hand, so that it has accurate timing information // for each TS packet // ------------------------------------------------------------ // This is a simplistic approach to the problem -- if it suffices, // it will be left as-is until something more sophisticated is // needed /* Make sure we've got a PCR buffer allocated, and that * its content is entirely unset (so this also serves as * a "reset" function). * * Returns 0 if all went well, 1 if something went wrong. */ static int start_TS_packet_buffer(TS_reader_p tsreader) { if (tsreader->pcrbuf == NULL) { tsreader->pcrbuf = malloc(SIZEOF_TS_PCR_BUFFER); if (tsreader->pcrbuf == NULL) { print_err("### Unable to allocate TS PCR read-ahead buffer\n"); return 1; } } memset(tsreader->pcrbuf, '\0', SIZEOF_TS_PCR_BUFFER); return 0; } /* Fill up the PCR read-ahead buffer with TS entries, until we hit * one (of the correct PID) with a PCR. * * Returns 0 if all went well, 1 if something went wrong, EOF if EOF was read. */ static int fill_TS_packet_buffer(TS_reader_p tsreader) { int ii; // Work out which TS packet we *will* have as our first (zeroth) entry tsreader->pcrbuf->TS_buffer_posn += tsreader->pcrbuf->TS_buffer_len; tsreader->pcrbuf->TS_buffer_len = 0; tsreader->pcrbuf->TS_buffer_next = 0; for (ii=0; iipcrbuf->TS_buffer_posn+ii); return 1; } } // Copy the data into our own read-ahead buffer memcpy(tsreader->pcrbuf->TS_buffer[ii],data,TS_PACKET_SIZE); err = split_TS_packet(data,&pid,&payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err) { fprint_err("### Error splitting TS packet %d\n", tsreader->pcrbuf->TS_buffer_posn+ii); return 1; } tsreader->pcrbuf->TS_buffer_len ++; if (pid != tsreader->pcrbuf->TS_buffer_pcr_pid) continue; // don't care about any PCR it might have get_PCR_from_adaptation_field(adapt,adapt_len,&got_pcr,&pcr); if (got_pcr) { tsreader->pcrbuf->TS_buffer_prev_pcr = tsreader->pcrbuf->TS_buffer_end_pcr; tsreader->pcrbuf->TS_buffer_end_pcr = pcr; tsreader->pcrbuf->TS_buffer_time_per_TS = pcr_unsigned_diff(tsreader->pcrbuf->TS_buffer_end_pcr, tsreader->pcrbuf->TS_buffer_prev_pcr) / tsreader->pcrbuf->TS_buffer_len; return 0; } } // If we ran out of buffer, then we've really got no choice but to give up // with an appropriate grumble fprint_err("!!! Next PCR not found when reading forwards" " (for %d TS packets, starting at TS packet %d)\n",PCR_READ_AHEAD_SIZE, tsreader->pcrbuf->TS_buffer_posn); return 1; } /* Set up the the "looping" buffered TS packet reader and let it know what its * PCR PID is. * * This must be called before any other _buffered_TS_packet function. * * - `pcr_pid` is the PID within which we should look for PCR entries * * Returns 0 if all went well, 1 if something went wrong (allocating space * for the TS PCR buffer). */ extern int prime_read_buffered_TS_packet(TS_reader_p tsreader, uint32_t pcr_pid) { if (start_TS_packet_buffer(tsreader)) return 1; tsreader->pcrbuf->TS_buffer_pcr_pid = pcr_pid; return 0; } /* Retrieve the first TS packet from the PCR read-ahead buffer, * complete with its calculated PCR time. * * prime_read_buffered_TS_packet() must have been called before this. * * This should be called the first time a TS packet is to be read * using the PCR read-ahead buffer. It "primes" the read-ahead mechanism * by performing the first actual read-ahead. * * - `pcr_pid` is the PID within which we should look for PCR entries * - `start_count` is the index of the current (last read) TS entry (which will * generally be the PMT). * - `data` returns a pointer to the TS packet data * - `pid` is its PID * - `pcr` is its PCR, calculated using the previous known PCR and * the following known PCR. * - `count` is the index of the returned TS packet in the file * * Note that, like read_next_TS_packet, we return a pointer to our data, * and, similarly, warn that it will go away next time this function * is called. * * Returns 0 if all went well, 1 if something went wrong, EOF if EOF was read. */ extern int read_first_TS_packet_from_buffer(TS_reader_p tsreader, uint32_t pcr_pid, uint32_t start_count, byte *data[TS_PACKET_SIZE], uint32_t *pid, uint64_t *pcr, uint32_t *count) { int err; if (tsreader->pcrbuf == NULL) { print_err("### TS PCR read-ahead buffer has not been set up\n" " Make sure prime_read_buffered_TS_packet() has been called\n"); return 1; } // Reset things tsreader->pcrbuf->TS_buffer_next = 0; tsreader->pcrbuf->TS_buffer_end_pcr = 0; tsreader->pcrbuf->TS_buffer_prev_pcr = 0; tsreader->pcrbuf->TS_buffer_posn = start_count; tsreader->pcrbuf->TS_buffer_len = 0; tsreader->pcrbuf->TS_buffer_pcr_pid = pcr_pid; tsreader->pcrbuf->TS_had_EOF = FALSE; // Read TS packets into our buffer until we find one with a PCR err = fill_TS_packet_buffer(tsreader); if (err) return err; // However, it's only the last packet (the one with the PCR) that // we are actually interested in tsreader->pcrbuf->TS_buffer_next = tsreader->pcrbuf->TS_buffer_len - 1; // Why, this is the very packet with its own PCR *pcr = tsreader->pcrbuf->TS_buffer_end_pcr; *data = tsreader->pcrbuf->TS_buffer[tsreader->pcrbuf->TS_buffer_next]; *pid = tsreader->pcrbuf->TS_buffer_pids[tsreader->pcrbuf->TS_buffer_next]; *count = start_count + tsreader->pcrbuf->TS_buffer_len; tsreader->pcrbuf->TS_buffer_next ++; return 0; } /* Retrieve the next TS packet from the PCR read-ahead buffer, * complete with its calculated PCR time. * * - `data` returns a pointer to the TS packet data * - `pid` is its PID * - `pcr` is its PCR, calculated using the previous known PCR and * the following known PCR. * * Note that, like read_next_TS_packet, we return a pointer to our data, * and, similarly, warn that it might go away next time this function * is called. * * Returns 0 if all went well, 1 if something went wrong, EOF if EOF was read. */ extern int read_next_TS_packet_from_buffer(TS_reader_p tsreader, byte *data[TS_PACKET_SIZE], uint32_t *pid, uint64_t *pcr) { int err; if (tsreader->pcrbuf == NULL) { print_err("### TS PCR read-ahead buffer has not been set up\n" " Make sure read_first_TS_packet_from_buffer() has been called\n"); return 1; } if (tsreader->pcrbuf->TS_buffer_next == tsreader->pcrbuf->TS_buffer_len) { if (tsreader->pcrbuf->TS_had_EOF) { // We'd already run out of look-ahead packets, so just return // our (deferred) end-of-file return EOF; } else { err = fill_TS_packet_buffer(tsreader); if (err == EOF) { // An EOF means we read the end-of-file before finding the next // TS packet with a PCR. We could stop here (returning EOF), but // whilst that would mean all TS packets had guaranteed accurate // PCRs, it would also mean that we would ignore some TS packets // at the end of the file. This proved unacceptable in practice, // so our second best choice is to "play out" using the last // known PCR rate-of-change. tsreader->pcrbuf->TS_had_EOF = TRUE; // remember we're playing out // Must move PCR start tsreader->pcrbuf->TS_buffer_prev_pcr = tsreader->pcrbuf->TS_buffer_end_pcr; // If we read nothing we must die now if (tsreader->pcrbuf->TS_buffer_next == tsreader->pcrbuf->TS_buffer_len) return err; } else if (err) return err; } } *data = tsreader->pcrbuf->TS_buffer[tsreader->pcrbuf->TS_buffer_next]; *pid = tsreader->pcrbuf->TS_buffer_pids[tsreader->pcrbuf->TS_buffer_next]; tsreader->pcrbuf->TS_buffer_next ++; if (tsreader->pcrbuf->TS_buffer_next == tsreader->pcrbuf->TS_buffer_len && !tsreader->pcrbuf->TS_had_EOF) { // Why, this is the very packet with its own PCR *pcr = tsreader->pcrbuf->TS_buffer_end_pcr; } else { *pcr = pcr_unsigned_wrap(tsreader->pcrbuf->TS_buffer_prev_pcr + tsreader->pcrbuf->TS_buffer_time_per_TS * tsreader->pcrbuf->TS_buffer_next); } return 0; } /* * Read the next TS packet, coping with looping, etc. * * prime_read_buffered_TS_packet() should have been called first. * * This is a convenience wrapper around read_first_TS_packet_from_buffer() * and read_next_TS_packet_from_buffer(). * * This differs from ``read_TS_packet`` in that it assumes that the * underlying code will already have read to the next PCR, so that * it can know the *actual* (PCR-based) time for each TS packet. * * - `tsreader` is the TS reader context * - `count` is a running count of TS packets read from this input * - `data` is a pointer to the data for the packet * - `pid` is the PID of the TS packet * - `pcr` is the PCR value (possibly calculated) for this TS packet * - if `max` is greater than zero, then at most `max` TS packets should * be read from the input * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) - i.e., rewind to `start_posn` and start again if * `count` reaches `max` (obviously only if `max` is greater than zero). * - `start_count` is the value `count` should have after we've looped back * to `start_posn` * - if `quiet` is true, then only error messages should be written out * * Returns 0 if all went well, 1 if something went wrong, EOF if `loop` is * false and either EOF was read, or `max` TS packets were read. */ extern int read_buffered_TS_packet(TS_reader_p tsreader, uint32_t *count, byte *data[TS_PACKET_SIZE], uint32_t *pid, uint64_t *pcr, int max, int loop, offset_t start_posn, uint32_t start_count, int quiet) { int err; if (max > 0 && (*count) >= (uint32_t)max) { if (loop) { if (!quiet) fprint_msg("Read %d packets, rewinding and continuing\n",max); err = seek_using_TS_reader(tsreader,start_posn); if (err) return 1; *count = start_count; } else { if (!quiet) fprint_msg("Stopping after %d TS packets\n",max); return EOF; } } // Read the next packet if (*count == start_count) { // XXX // XXX We *strongly* assume that we will find two PCRs (in the // XXX required distance -- I think it's best to declare that // XXX "not a problem", by fiat. // XXX // XXX But is it acceptable that we ignore any TS packets before // XXX the first packet with a PCR? Probably more so than that we // XXX should ignore any packets at the end of the file. // XXX err = read_first_TS_packet_from_buffer(tsreader, tsreader->pcrbuf->TS_buffer_pcr_pid, start_count,data,pid,pcr,count); if (err) { if (err == EOF) { print_err("### EOF looking for first PCR\n"); return 1; } else { fprint_err("### Error reading TS packet %d, looking for first PCR\n", *count); return 1; } } } else { err = read_next_TS_packet_from_buffer(tsreader,data,pid,pcr); if (err) { if (err == EOF) { if (!loop) return EOF; if (!quiet) fprint_msg("EOF (after %d TS packets), rewinding and continuing\n", *count); } else { fprint_err("### Error reading TS packet %d\n",*count); if (!loop) return 1; if (!quiet) print_msg("!!! Rewinding and continuing anyway\n"); } err = seek_using_TS_reader(tsreader,start_posn); if (err) return 1; *count = start_count; err = read_first_TS_packet_from_buffer(tsreader, tsreader->pcrbuf->TS_buffer_pcr_pid, start_count,data,pid,pcr,count); if (err) { print_err("### Failed rewinding\n"); return 1; } } else (*count) ++; } return 0; } // ------------------------------------------------------------ // Packet interpretation // ------------------------------------------------------------ /* * Retrieve the PCR (if any) from a TS packet's adaptation field * * - `adapt` is the adaptation field content * - `adapt_len` is its length * - `got_PCR` is TRUE if the adaptation field contains a PCR * - `pcr` is then the PCR value itself */ extern void get_PCR_from_adaptation_field(byte adapt[], int adapt_len, int *got_pcr, uint64_t *pcr) { if (adapt_len == 0 || adapt == NULL) *got_pcr = FALSE; else if (adapt[0] & 0x10) // We have a PCR { *got_pcr = TRUE; // The program_clock_reference_base // NB: Force the first byte to be unsigned 64 bit, or else on Windows // it tends to get shifted as a signed integer, and sign-extended, // before it gets turned unsigned (which is probably the "correct" // behaviour according to the standard. oh well). *pcr = ((uint64_t)adapt[1] << 25) | (adapt[2] << 17) | (adapt[3] << 9) | (adapt[4] << 1) | (adapt[5] >> 7); // Plus the program clock reference extension *pcr = ((*pcr) * 300) + ((adapt[5] & 1) << 8) + adapt[6]; } else *got_pcr = FALSE; return; } /* * Report on the contents of this TS packet's adaptation field * * - `adapt` is the adaptation field content * - `adapt_len` is its length * * Returns 0 if all went well, 1 if something went wrong. */ extern void report_adaptation_field(byte adapt[], int adapt_len) { int got_pcr; uint64_t pcr; if (adapt_len == 0 || adapt == NULL) return; fprint_msg(" Adaptation field len %3d [flags %02x]",adapt_len,adapt[0]); if (adapt[0] != 0) { print_msg(":"); if (ON(adapt[0],0x80)) print_msg(" discontinuity "); if (ON(adapt[0],0x40)) print_msg(" random access "); if (ON(adapt[0],0x20)) print_msg(" ES-priority "); if (ON(adapt[0],0x10)) print_msg(" PCR "); if (ON(adapt[0],0x08)) print_msg(" OPCR "); if (ON(adapt[0],0x04)) print_msg(" splicing "); if (ON(adapt[0],0x02)) print_msg(" private "); if (ON(adapt[0],0x01)) print_msg(" extension "); } print_msg("\n"); get_PCR_from_adaptation_field(adapt,adapt_len,&got_pcr,&pcr); if (got_pcr) { fprint_msg(" .. PCR %12" LLU_FORMAT_STUMP "\n", pcr); } return; } /* * Report on the timing information from this TS packet's adaptation field * * - if `times` is non-NULL, then timing information (derived from the PCR) * will be calculated and reported * - `adapt` is the adaptation field content * - `adapt_len` is its length * - `packet_count` is a count of how many TS packets up to now * * Returns 0 if all went well, 1 if something went wrong. */ extern void report_adaptation_timing(timing_p times, byte adapt[], int adapt_len, int packet_count) { int got_pcr; uint64_t pcr; if (adapt_len == 0 || adapt == NULL || times == NULL) return; get_PCR_from_adaptation_field(adapt,adapt_len,&got_pcr,&pcr); if (got_pcr) { fprint_msg(" .. PCR %12" LLU_FORMAT_STUMP, pcr); if (!times->had_first_pcr) { times->last_pcr_packet = times->first_pcr_packet = packet_count; times->last_pcr = times->first_pcr = pcr; times->had_first_pcr = TRUE; } else { if (pcr < times->last_pcr) fprint_msg(" Discontinuity: PCR was %7" LLU_FORMAT_STUMP ", now %7" LLU_FORMAT_STUMP,times->last_pcr,pcr); else { fprint_msg(" Mean byterate %7" LLU_FORMAT_STUMP, ((packet_count - times->first_pcr_packet) * TS_PACKET_SIZE) * TWENTY_SEVEN_MHZ / pcr_unsigned_diff(pcr, times->first_pcr)); fprint_msg(" byterate %7" LLU_FORMAT_STUMP, ((packet_count - times->last_pcr_packet) * TS_PACKET_SIZE) * TWENTY_SEVEN_MHZ / pcr_unsigned_diff(pcr, times->last_pcr)); } } times->last_pcr_packet = packet_count; times->last_pcr = pcr; print_msg("\n"); } return; } /* * Report on the contents of this TS packet's payload. The packet is assumed * to have a payload that is (part of) a PES packet. * * - if `show_data` then the data for the PES packet will also be shown * - `stream_type` is the stream type of the data, or -1 if it is not * known * - `payload` is the payload of the TS packet. We know it can't be more * than 184 bytes long, because of the packet header bytes. * - regardless, `payload_len` is the actual length of the payload. * * Returns 0 if all went well, 1 if something went wrong. */ extern void report_payload(int show_data, int stream_type, byte payload[MAX_TS_PAYLOAD_SIZE], int payload_len, int payload_unit_start_indicator) { if (payload_unit_start_indicator) report_PES_data_array2(stream_type,payload,payload_len, show_data?1000:0); else if (show_data) print_data(TRUE,"Data",payload,payload_len,1000); } /* * Extract the program list from a PAT packet (PID 0x0000). * * Handles the result of calling build_psi_data() for this PAT. * * - if `verbose`, then report on what we're doing * - `data` is the data for the PAT packet. * - `data_len` is the length of said data. * - `prog_list` is the list of program numbers versus PIDs. * * Returns 0 if all went well, 1 if something went wrong. */ extern int extract_prog_list_from_pat(int verbose, byte data[], int data_len, pidint_list_p *prog_list) { int table_id; int section_syntax_indicator,zero_bit,reserved1; int section_length; int transport_stream_id; int version_number; int current_next_indicator; int section_number; int last_section_number; uint32_t crc = 0; uint32_t check_crc; byte *program_data; int program_data_len; int err; if (data_len == 0) { print_err("### PAT data has zero length\n"); return 1; } if (data == NULL) { print_err("### PAT data is NULL\n"); return 1; } if (DEBUG) print_data(TRUE,"Data",data,data_len,1000); // The table id in a PAT should be 0 table_id = data[0]; if (table_id != 0) { fprint_err("### PAT table id is %0#8x, should be 0\n",table_id); return 1; } // Check bits - do we actually want to check these? section_syntax_indicator = (data[1] & 0x80) >> 7; zero_bit = (data[1] & 0x40) >> 6; reserved1 = (data[1] & 0x30) >> 4; if (section_syntax_indicator != 1 && report_bad_reserved_bits) print_err("!!! PAT: section syntax indicator is 0, not 1\n"); if (zero_bit != 0 && report_bad_reserved_bits) print_err("!!! PAT: zero bit is 1, not 0\n"); if (reserved1 != 3 && report_bad_reserved_bits) fprint_err("!!! PAT: reserved1 is %d, not 3\n",reserved1); section_length = ((data[1] & 0xF) << 8) | data[2]; if (verbose) fprint_msg(" section length: %03x (%d)\n", section_length,section_length); // If the section length doesn't match our data length, we've got problems // (remember, the section_length counts bytes after the section_length field) if (section_length > data_len - 3) { fprint_err("### PAT section length %d is more than" " length of remaining data %d\n",section_length,data_len-3); return 1; } else if (section_length < data_len - 3) { fprint_err("!!! PAT section length %d does not use all of" " remaining data %d\n",section_length,data_len-3); // Adjust it and carry on data_len = section_length + 3; } data_len = section_length + 3; transport_stream_id = (data[3] << 8) | data[4]; if (verbose) fprint_msg(" transport stream id: %04x\n",transport_stream_id); // reserved2 = (data[5] & 0xC0) >> 14; version_number = (data[5] & 0x3E) >> 1; current_next_indicator = data[5] & 0x1; section_number = data[6]; last_section_number = data[7]; if (verbose) fprint_msg(" version number %02x, current next %x, section number %x, last" " section number %x\n",version_number,current_next_indicator, section_number,last_section_number); // 32 bits at the end of a program association section is reserved for a CRC // (OK, let's extract it stupidly...) crc = (crc << 8) | data[data_len-4]; crc = (crc << 8) | data[data_len-3]; crc = (crc << 8) | data[data_len-2]; crc = (crc << 8) | data[data_len-1]; // Let's check the CRC check_crc = crc32_block(0xffffffff,data,data_len); if (check_crc != 0) { fprint_err("!!! Calculated CRC for PAT is %08x, not 00000000" " (CRC in data was %08x)\n",check_crc,crc); return 1; } // (remember the section length is for the bytes *after* the section // length field, so for data[3...]) program_data = data + 8; program_data_len = data_len - 8 - 4; // The "-4" is for the CRC //print_data(TRUE,"Rest:",program_data,program_data_len,1000); err = build_pidint_list(prog_list); if (err) return 1; while (program_data_len > 0) { int program_number = (program_data[0] << 8) | program_data[1]; uint32_t pid = ((program_data[2] & 0x1F) << 8) | program_data[3]; // A program_number of 0 indicates the network ID, so ignore it and // don't append to the program list - rrw 2004-10-13 if (!program_number) { if (verbose) fprint_msg(" Network ID %04x (%3d)\n", pid, pid); } else { if (verbose) fprint_msg(" Program %03x (%3d) -> PID %04x (%3d)\n", program_number,program_number,pid,pid); err = append_to_pidint_list(*prog_list,pid,program_number); if (err) return 1; } program_data = program_data + 4; program_data_len = program_data_len - 4; } return 0; } static const char * dvb_component_type3_str(int component_type) { switch (component_type) { case 0x01: return "EBU Teletext subtitles"; case 0x02: return "associated EBU Teletext"; case 0x03: return "VBI data"; case 0x10: return "DVB subtitles (normal) with no monitor aspect ratio criticality"; case 0x11: return "DVB subtitles (normal) for display on 4:3 aspect ratio monitor"; case 0x12: return "DVB subtitles (normal) for display on 16:9 aspect ratio monitor"; case 0x13: return "DVB subtitles (normal) for display on 2.21:1 aspect ratio monitor"; case 0x14: return "DVB subtitles (normal) for display on a high definition monitor"; case 0x20: return "DVB subtitles (for the hard of hearing) with no monitor aspect ratio criticality"; case 0x21: return "DVB subtitles (for the hard of hearing) for display on 4:3 aspect ratio monitor"; case 0x22: return "DVB subtitles (for the hard of hearing) for display on 16:9 aspect ratio monitor"; case 0x23: return "DVB subtitles (for the hard of hearing) for display on 2.21:1 aspect ratio monitor"; case 0x24: return "DVB subtitles (for the hard of hearing) for display on a high definition monitor"; default: if (component_type >= 0xb0 && component_type <= 0xfe) { return "user defined"; } break; } return "reserved"; } static const char * const descriptor_names[] = { "Reserved", // 0 "Forbidden", // 1 "Video stream", // 2 "Audio stream", // 3 "Hierarchy", // 4 "Registration", // 5 "Data stream alignment", // 6 "Target background grid", // 7 "Video window", // 8 "CA", // 9 "ISO 639 language", // 10 "System clock", // 11 "Multiplex buffer utilization", // 12 "Copyright", // 13 "Maximum bitrate", // 14 "Private data indicator", // 15 "Smoothing buffer", // 16 "STD", // 17 "IBP", // 18 "Defined in ISO/IEC 13818-6", // 19 "Defined in ISO/IEC 13818-6", // 20 "Defined in ISO/IEC 13818-6", // 21 "Defined in ISO/IEC 13818-6", // 22 "Defined in ISO/IEC 13818-6", // 23 "Defined in ISO/IEC 13818-6", // 24 "Defined in ISO/IEC 13818-6", // 25 "Defined in ISO/IEC 13818-6", // 26 "MPEG-4 video", // 27 "MPEG-4 audio", // 28 "IOD", // 29 "SL", // 30 "FMC", // 31 "External ES ID", // 32 "MuxCode", // 33 "FmxBufferSize", // 34 "MultiplexBuffer", // 35 "Content labeling", // 36 "Metadata pointer", // 37 "Metadata", // 38 "Metadata STD", // 39 "AVC video descriptor", // 40 "IPMP (defined in ISO/IEC 13818-11, MPEG-2 IPMP)", // 41 "AVC timing and HRD descriptor", // 42 "MPEG-2 AAC audio", // 43 "FlexMuxTiming", // 44 "MPEG-4 text", // 45 "MPEG-4 audio extension", // 46 "auxiliary video stream", // 47 "SVC extension", // 48 "MVC extension", // 49 "J2K video descriptor", // 50 "MVC operation point descriptor", // 51 "MPEG2 stereoscopic video format", // 52 "Stereoscopic_program_info_descriptor", // 53 "Stereoscopic_video_info_descriptor", // 54 "Transport_profile_descriptor", // 55 "HEVC video descriptor", // 56 "Reserved (57)", // 57 "Reserved (58)", // 58 "Reserved (59)", // 59 "Reserved (60)", // 60 "Reserved (61)", // 61 "Reserved (62)", // 62 "Extension descriptor", // 63 }; // From ATSC A/52B section A3.4 // N.B. Horizontal lines in the table represent valid stop points static void print_ac3_audio_descriptor(const int is_msg, const byte * const buf, const int len) { const byte * p = buf; const byte * const eop = p + len; static const char * const sample_rate_txt[8] = { "48k", "44k1", "32k", "Reserved(3)", "48k or 44.1k", "48k or 32k", "44.1k or 32k", "48k or 44.1k or 32k" }; static const int bit_rate_n[32] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640, }; static const char * const dsurmod_txt[4] = { "Unknown", "Not Dolby suuround encoded", "Dolby surround encoded", "Reserved" }; static const char * const num_channels_txt[16] = { "1 + 1", "1/0", "2/0", "3/0", "2/1", "3/1", "2/2", "3/2", "1", "<=2", "<=3", "<=4", "<=5", "<=6", "Reserved(14)", "Reserved(15)" }; static const char * const priority_txt[4] = { "Reserved(0)", "Primary Audio", "Other Audio", "Not specified" }; unsigned int nc; unsigned int bsmod; if (p >= eop) { goto too_short; } fprint_msg_or_err(is_msg, "sample_rate: %s, bsid: %d", sample_rate_txt[*p >> 5], *p & 0x1f); ++p; fprint_msg_or_err(is_msg, ", bit_rate: %s %dk, dsurmod: %s", *p >> 7 ? "Upper" : "Exact", bit_rate_n[(*p >> 2) & 31], dsurmod_txt[*p & 3]); if (++p >= eop) { goto too_short; } nc = (*p >> 1) & 0x0f; bsmod = *p >> 5; fprint_msg_or_err(is_msg, ", bsmod: %d, num_channels: %s, full_svc: %d", bsmod, num_channels_txt[nc], *p & 1); if (++p >= eop) { goto done; } fprint_msg_or_err(is_msg, ", langcod: %d", *p); if (nc == 0) { if (++p >= eop) { goto done; } fprint_msg_or_err(is_msg, ", langcod2: %d", *p); } if (++p >= eop) { goto done; } if (bsmod < 2) { fprint_msg_or_err(is_msg, ", mainid: %d, priority: %s", *p >> 5, priority_txt[(*p >> 3) & 3]); if ((*p & 7) != 7) { fprint_msg_or_err(is_msg, ", reserved(7): %d", *p & 7); } } else { fprint_msg_or_err(is_msg, ", asvcflags: %#08x", *p); } if (++p >= eop) { goto done; } { unsigned int textlen = *p >> 1; const int utf16 = !(*p & 1); fprint_msg_or_err(is_msg, ", text(%c): ", utf16 ? 'U' : 'S'); if (p + textlen >= eop) { goto too_short; } if (textlen == 0) { fprint_msg_or_err(is_msg, ""); } else if (!utf16) { fprint_msg_or_err(is_msg, "\""); while (textlen-- != 0) { fprint_msg_or_err(is_msg, "%c", *++p); } fprint_msg_or_err(is_msg, "\""); } else { fprint_msg_or_err(is_msg, "??"); p += textlen; } } if (++p >= eop) { goto done; } { const int language_flag = *p >> 7; const int language_flag_2 = (*p >> 6) & 1; if ((*p & 0x1f) != 0x1f) { fprint_msg_or_err(is_msg, ", reserved(0x1f): %#x", *p & 0x1f); } if (language_flag) { if (++p + 2 >= eop) { goto too_short; } fprint_msg_or_err(is_msg, ", language: %c%c%c", p[0], p[1], p[2]); p += 2; } if (language_flag_2) { if (++p + 2 >= eop) { goto too_short; } fprint_msg_or_err(is_msg, ", language_2: %c%c%c", p[0], p[1], p[2]); p += 2; } } if (++p >= eop) { goto done; } print_data(is_msg, "additional_info: ", p, eop - p,100); done: fprint_msg_or_err(is_msg, "\n"); return; too_short: fprint_msg_or_err(is_msg, "; ### block short ###\n"); } static void print_HEVC_descriptor(const int is_msg, const byte * const buf, const int len) { const uint8_t * p = buf; const byte * const eop = p + len; static const char * const prog_interlace[4] = { "unknown scan source", "interlaced source", "progressive source", "mixed scan source" }; if (len == 9) { // I've seen a number of these but I can't find a standard print_data(is_msg, "HEVC video descriptor ### bad length", buf, len ,100); return; } fprint_msg_or_err(is_msg, "HEVC video descriptor:"); if (p >= eop) { goto too_short; } fprint_msg_or_err(is_msg, " profile_space=%d, tier_flag=%d, profile_idc=%d", *p >> 6, (*p >> 5) & 1, *p & 0x1f); if (++p + 3 >= eop) { goto too_short; } fprint_msg_or_err(is_msg, ", profile_compatability=%#08x", uint_32_be(p)); if ((p += 4) + 5 >= eop) { goto too_short; } fprint_msg_or_err(is_msg, ", %s%s%s", prog_interlace[*p >> 6], *p & 0x20 ? ", non_packed" : "", *p & 0x10 ? ", frame_only" : ""); if ((*p & 0xf) != 0 || p[1] != 0 || p[2] != 0 || p[3] != 0 || p[4] != 0 || p[5] != 0) { fprint_msg_or_err(is_msg, ", ### reserved_zero_44bits=0x%x%02x%08x", *p & 0xf, p[1], uint_32_be(p + 2)); } if ((p += 6) >= eop) { goto too_short; } fprint_msg_or_err(is_msg, ", level=%d.%d", *p / 30, *p % 30); if (++p >= eop) { goto too_short; } fprint_msg_or_err(is_msg, "%s%s", *p & 0x40 ? ", still" : "", *p & 0x20 ? ", 24hr" : ""); if ((*p & 0x1f) != 0x1f) { fprint_msg_or_err(is_msg, ", ### reserved=%#02x", *p & 0x1f); } if ((*p++ & 0x80) != 0) { fprint_msg_or_err(is_msg, ", temporal_id"); if (p + 2 >= eop) { goto too_short; } if ((*p >> 3) != 0x1f) { fprint_msg_or_err(is_msg, " ### reserved=%#02x", *p & 0x1f); } fprint_msg_or_err(is_msg, " min=%d", *p & 7); ++p; if ((*p >> 3) != 0x1f) { fprint_msg_or_err(is_msg, " ### reserved=%#02x", *p & 0x1f); } fprint_msg_or_err(is_msg, " max=%d", *p & 7); ++p; } fprint_msg_or_err(is_msg,"\n"); return; too_short: fprint_msg_or_err(is_msg, "; ### block short ###\n"); } /* * Print out information about program descriptors * (either from the PMT program info, or the PMT/stream ES info) * * - if `is_msg` then print as a message, otherwise as an error * - `leader1` and `leader2` are the text to write at the start of each line * (either or both may be NULL) * - `desc_data` is the data containing the descriptors * - `desc_data_len` is its length * * Returns 0 if all went well, 1 if something went wrong * * If you want to interpret more descriptors then ITU-T J.94 is the standard */ extern int print_descriptors(int is_msg, char *leader1, char *leader2, byte *desc_data, int desc_data_len) { byte data_len = desc_data_len; byte *data = desc_data; while (data_len >= 2) { byte tag = data[0]; byte this_length = data[1]; data += 2; data_len -= 2; if (this_length > data_len) { // Not much we can do - try giving up? fprint_msg_or_err(is_msg,"Descriptor %x says length %d, but only %d bytes left\n", tag,this_length,data_len); return 1; // Hmm - well, maybe } if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1); if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2); { int ii; uint32_t temp_u; switch (tag) { case 5: fprint_msg_or_err(is_msg,"Registration "); if (this_length >= 4) { for (ii=0; ii<4; ii++) { if (isprint(data[ii])) fprint_msg_or_err(is_msg,"%c",data[ii]); else fprint_msg_or_err(is_msg,"<%02x>",data[ii]); } if (this_length > 4) for (ii=4; ii < this_length; ii++) fprint_msg_or_err(is_msg," %02x",data[ii]); } fprint_msg_or_err(is_msg,"\n"); break; case 9: // I see this in data, so might as well "explain" it fprint_msg_or_err(is_msg,"Conditional access: "); temp_u = (data[0] << 8) | data[1]; fprint_msg_or_err(is_msg,"id %04x (%d) ",temp_u,temp_u); temp_u = ((data[2] & 0x1F) << 8) | data[3]; fprint_msg_or_err(is_msg,"PID %04x (%d) ",temp_u,temp_u); if (data_len > 4) print_data(is_msg,"data",&data[4],data_len-4,data_len-4); else fprint_msg_or_err(is_msg,"\n"); break; case 10: // We'll assume the length is a multiple of 4 fprint_msg_or_err(is_msg,"Languages: "); for (ii = 0; ii < this_length/4; ii++) { byte audio_type; if (ii > 0) fprint_msg_or_err(is_msg,", "); fprint_msg_or_err(is_msg,"%c",*(data+(ii*4)+0)); fprint_msg_or_err(is_msg,"%c",*(data+(ii*4)+1)); fprint_msg_or_err(is_msg,"%c",*(data+(ii*4)+2)); audio_type = *(data+(ii*4)+3); switch (audio_type) { case 0: /*fprint_msg_or_err(is_msg,"/undefined");*/ break; // clearer to say nowt? case 1: fprint_msg_or_err(is_msg,"/clean effects"); break; case 2: fprint_msg_or_err(is_msg,"/hearing impaired"); break; case 3: fprint_msg_or_err(is_msg,"/visual impaired commentary"); break; default: fprint_msg_or_err(is_msg,"/reserved:0x%02x",audio_type); break; } } fprint_msg_or_err(is_msg,"\n"); break; case 40: { const uint8_t * p = data; unsigned int t; fprint_msg_or_err(is_msg,"AVC video descriptor: "); if (this_length != 4) { if (this_length < 4) { // Give up if too short fprint_msg_or_err(is_msg,"### descriptor too short %d ###\n", this_length); break; } else { // Complain but carry on if too long fprint_msg_or_err(is_msg,"### descriptor too long %d ###: \n", this_length); } } fprint_msg_or_err(is_msg,"profile idc: %d, ", *p++); fprint_msg_or_err(is_msg,"constraint_set["); t = *p; for (ii = 0; ii != 6; ++ii) { fprint_msg_or_err(is_msg,"%c", (t & 0x80) != 0 ? '0' + ii : '-'); t <<= 1; } fprint_msg_or_err(is_msg,"], AVC_compatible_flags: %d, ", *p++ & 3); fprint_msg_or_err(is_msg,"level_idc: %d, ", *p++); fprint_msg_or_err(is_msg,"AVC_still_present: %d, ", (*p >> 7) & 1); fprint_msg_or_err(is_msg,"AVC_24_hour_picture_flag: %d, ", (*p >> 6) & 1); fprint_msg_or_err(is_msg,"Frame_Packing_SEI_not_present_flag: %d, ", (*p >> 5) & 1); fprint_msg_or_err(is_msg,"reserved: %#x", *p & 0x1f); fprint_msg_or_err(is_msg,"\n"); break; } case 42: { const uint8_t * p = data; fprint_msg_or_err(is_msg,"AVC timing and HRD descriptor: "); fprint_msg_or_err(is_msg,"hrd_management_valid_flag: %d, ", (*p & 0x80) != 0); if ((*p & 0x7e) != 0x7e) { fprint_msg_or_err(is_msg,"reserved: %#x, ", (*p & 0x7e) >> 1); } if ((*p++ & 1) != 0) // picture_and_timing_info_present { int flag90 = *p >> 7; uint32_t n = 1, k = 300; uint32_t ntick; if (flag90) { fprint_msg_or_err(is_msg,"90kHz_flag, ", *p & 0x7f); } if ((*p & 0x7f) != 0x7f) { fprint_msg_or_err(is_msg,"reserved: %#x, ", *p & 0x7f); } ++p; if (!flag90) { n = uint_32_be(p); p += 4; k = uint_32_be(p); p += 4; fprint_msg_or_err(is_msg,"N/K: %u/%u, ", n, k); } ntick = uint_32_be(p); p += 4; fprint_msg_or_err(is_msg,"num_units_in_tick: %u, ", ntick); if (k == 0 || ntick == 0) fprint_msg_or_err(is_msg,"(frame rate: \?\?\?), "); else fprint_msg_or_err(is_msg,"(frame rate: %.6g), ", ((double)n * 27000000.0) / ((double)k * (double)ntick) / 2.0); } fprint_msg_or_err(is_msg,"fixed_frame_rate_flag: %u, ", *p >> 7); fprint_msg_or_err(is_msg,"temporal_poc_flag: %u, ", (*p >> 6) & 1); fprint_msg_or_err(is_msg,"picture_to_display_conversion_flag: %u", (*p >> 5) & 1); if ((*p & 0x1f) != 0x1f) { fprint_msg_or_err(is_msg,", reserved: %#x", *p & 0x1f); } fprint_msg_or_err(is_msg,"\n"); } break; case 52: fprint_msg_or_err(is_msg,"MPEG2 stereoscopic video format: "); if (this_length != 1) { if (this_length < 1) { // Give up if too short fprint_msg_or_err(is_msg,"### descriptor too short %d ###\n", this_length); break; } else { // Complain but carry on if too long fprint_msg_or_err(is_msg,"### descriptor too long %d ###: \n", this_length); } } if ((data[0] & 0x80) != 0) { fprint_msg_or_err(is_msg,"arrangement not present: reserved: %#x", data[0] & 0x7f); } else { fprint_msg_or_err(is_msg,"arrangement: "); switch (data[0]) { case 3: fprint_msg_or_err(is_msg,"S3D side by side"); break; case 4: fprint_msg_or_err(is_msg,"S3D top and bottom"); break; case 8: fprint_msg_or_err(is_msg,"2D video"); break; default: fprint_msg_or_err(is_msg,"reserved: %#x", data[0] & 0x7f); break; } } fprint_msg_or_err(is_msg,"\n"); break; case 56: print_HEVC_descriptor(is_msg, data, this_length); break; case 0x56: // teletext for (ii = 0; ii < this_length; ii += 5) { int jj; int teletext_type, teletext_magazine, teletext_page; if (ii == 0) fprint_msg_or_err(is_msg,"Teletext: "); else { if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1); if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2); fprint_msg_or_err(is_msg," "); } fprint_msg_or_err(is_msg,"language="); for (jj=ii; jj",data[jj]); } teletext_type = (data[ii+3] & 0xF8) >> 3; teletext_magazine = (data[ii+3] & 0x07); teletext_page = data[ii+4]; fprint_msg_or_err(is_msg,", type="); switch (teletext_type) { case 1: fprint_msg_or_err(is_msg,"Initial"); break; case 2: fprint_msg_or_err(is_msg,"Subtitles"); break; case 3: fprint_msg_or_err(is_msg,"Additional info"); break; case 4: fprint_msg_or_err(is_msg,"Programme schedule"); break; case 5: fprint_msg_or_err(is_msg,"Hearing impaired subtitles"); break; default: fprint_msg_or_err(is_msg,"%x (reserved)",teletext_type); break; } fprint_msg_or_err(is_msg,", magazine %d, page %x",teletext_magazine,teletext_page); fprint_msg_or_err(is_msg,"\n"); } break; case 0x59: { fprint_msg_or_err(is_msg, "subtitling_descriptor(s):\n"); for (ii = 0; ii + 8 <= this_length; ii += 8) { char lang[4]; unsigned int subtitling_type = data[ii + 3]; unsigned int composition_page_id = (data[ii + 4] << 8) | data[ii + 5]; unsigned int ancillary_page_id = (data[ii + 6] << 8) | data[ii + 7]; lang[0] = data[ii + 0]; lang[1] = data[ii + 1]; lang[2] = data[ii + 2]; lang[3] = 0; if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1); if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2); fprint_msg_or_err(is_msg," language='%s', subtitling_type=%u\n", lang, subtitling_type); if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1); if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2); fprint_msg_or_err(is_msg, " (%s)\n", dvb_component_type3_str(subtitling_type)); if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1); if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2); fprint_msg_or_err(is_msg, " composition_page_id=%u, ancillary_page_id=%u\n", composition_page_id, ancillary_page_id); } if (ii < this_length) fprint_msg_or_err(is_msg, "### %d spare bytes at end of descriptor\n", this_length - ii); break; } case 0x6A: print_data(is_msg,"DVB AC-3",data,this_length,100); break; case 0x81: // print_data(is_msg,"ATSC AC-3",data,this_length,100); fprint_msg_or_err(is_msg, "ATSC AC-3: "); print_ac3_audio_descriptor(is_msg, data, this_length); break; default: { char temp_c[50]; // twice as much as I need... snprintf(temp_c, sizeof(temp_c), "%s (%d)", tag < sizeof(descriptor_names)/sizeof(descriptor_names[0]) ? descriptor_names[tag] : tag < 64 ? "Reserved" : "User Private", tag); print_data(is_msg,temp_c,data,this_length,100); } break; } } data_len -= this_length; data += this_length; } return 0; } /* * Given a TS packet, extract the (next bit of) a PAT/PMT's data. * * - if `verbose`, then report on what we're doing * - `payload` is the payload of the current TS packet. We know it can't be * more than 184 bytes long, because of the packet header bytes. * - regardless, `payload_len` is the actual length of the payload. * - `pid` is the PID of this TS packet. * - `data` is the data array for the whole of the data of this PSI. * If it is passed as NULL, then the TS packet must be the first for * this PSI, and this function will malloc an array of the appropriate * length (and return it here). If it is non-NULL, then it is partially * full. * - `data_len` is the actual length of the `data` array -- if `data` is NULL * then this will be set by the function. * - `data_used` is how many bytes of data are already in the `data` array. * This will be updated by this function - if it is returned as equal to * `data_len`, then the PAT/PMT packet data is complete. * * Usage: * * If a PSI packet has PUSI set, then it is the first packet of said PSI * (which, for our purposes, means PAT or PMT). If it does not, then it * is a continuation. If PUSI was set, call this with ``data`` NULL, otherwise * pass it some previous data to continue. * * Returns 0 if all went well, 1 if something went wrong. */ extern int build_psi_data(int verbose, byte payload[MAX_TS_PAYLOAD_SIZE], int payload_len, uint32_t pid, byte **data, int *data_len, int *data_used) { byte *packet_data; int packet_data_len; int pointer; int section_length; if (payload_len == 0) { print_err("### PMT payload has zero length\n"); return 1; } if (payload == NULL) { print_err("### PMT payload is NULL\n"); return 1; } if (*data == NULL) { // We have the first section of a PSI packet, which contains the pointer // field - deal with it pointer = payload[0]; if (pointer > (payload_len - 1)) { fprint_err("### PMT payload: pointer is %d, which is off the end of" " the packet (length %d)\n",pointer,payload_len); return 1; } // if (DEBUG) print_data(TRUE,"PMT",payload,payload_len,1000); packet_data = payload + pointer + 1; packet_data_len = payload_len - pointer - 1; if (DEBUG) print_data(TRUE,"Data",packet_data,packet_data_len,1000); section_length = ((packet_data[1] & 0xF) << 8) | packet_data[2]; #if 0 // XXX print_msg("===========================================\n"); print_data(TRUE,"build_pmt_data(new)",packet_data,packet_data_len,packet_data_len); #endif *data_len = section_length + 3; // Beware - if our PMT is shorter than our TS packet, we only want to // "use" the data that belongs to our PMT, not the rest of the packet // (which is hopefully full of 0xFF anyway) // We want to get this right because our callers decide if they've // finished reading a PMT by comparing data_used with data_len. if (packet_data_len > *data_len) *data_used = *data_len; else *data_used = packet_data_len; *data = malloc(*data_len); if (*data == NULL) { print_err("### Unable to malloc PSI data array\n"); return 1; } memcpy(*data,packet_data,*data_len); } else { // This is a continuation of a PSI packet - it doesn't contain a pointer // field, so our data is just data int space_left = *data_len - *data_used; packet_data = payload; packet_data_len = payload_len; if (DEBUG) print_data(TRUE,"Data",packet_data,packet_data_len,1000); #if 0 // XXX print_msg("===========================================\n"); print_data(TRUE,"build_pmt_data(old)",packet_data,packet_data_len,100); #endif if (space_left > packet_data_len) { // We have more than enough room - use all of this packet memcpy(*data + *data_used, packet_data, packet_data_len); *data_used += packet_data_len; } else { // We have more than enough data - use what we need // (we assume the rest will be 0xFF padded, but shan't check) memcpy(*data + *data_used, packet_data, space_left); *data_used += space_left; } } return 0; } /* * Extract the program map table from a PMT packet. * * Handles the result of calling build_psi_data() for this PMT. * * - if `verbose`, then report on what we're doing * - `data` is the data for the PMT packet. * - `data_len` is the length of said data. * - `pid` is the PID of this PMT * - `pmt` is the new PMT datastructure * * Returns 0 if all went well, 1 if something went wrong. */ extern int extract_pmt(int verbose, byte data[], int data_len, uint32_t pid, pmt_p *pmt) { int table_id; int section_syntax_indicator,zero_bit,reserved; uint16_t program_number; uint32_t pcr_pid; int section_length; int version_number; int current_next_indicator; int section_number; int last_section_number; int program_info_length; uint32_t crc = 0; uint32_t check_crc; byte *stream_data; int stream_data_len; int err; if (data_len == 0) { print_err("### PMT data has zero length\n"); return 1; } if (data == NULL) { print_err("### PMT data is NULL\n"); return 1; } if (DEBUG) print_data(TRUE,"Data",data,data_len,1000); // Check the table id (maybe this should be done by our caller?) table_id = data[0]; if (table_id != 2) { // The table_id for a PMT is 2. // A PAT may also reference user private tables, and I've seen data with // other table values (including FF) as well: if (0x03 <= table_id && table_id <=0xFE) // user private table { if (verbose) { fprint_msg(" 'PMT' with PID %04x is user private table %02x\n",pid,table_id); print_data(TRUE," Data",data,data_len,20); } } else { if (0x03 <= table_id && table_id <= 0x3F) fprint_err("### PMT table id is %0#x (H.222 / ISO/IEC 13818-1" " reserved), should be 2\n",table_id); else fprint_err("### PMT table id is %0#x (%s), should be 2\n", table_id,(table_id==0x00?"PAT": table_id==0x01?"CAT": table_id==0xFF?"Forbidden":"???")); print_data(FALSE," Data",data,data_len,20); } // Best we can do is to pretend it didn't happen *pmt = build_pmt(0,0,0); // empty "PMT" with program number 0, PCR PID 0 if (*pmt == NULL) return 1; return 0; } // Check bits section_syntax_indicator = (data[1] & 0x80) >> 7; zero_bit = (data[1] & 0x40) >> 6; reserved = (data[1] & 0x30) >> 4; if (section_syntax_indicator != 1 && report_bad_reserved_bits) print_err("!!! PMT: section syntax indicator is 0, not 1\n"); if (zero_bit != 0 && report_bad_reserved_bits) print_err("!!! PMT: zero bit is 1, not 0\n"); if (reserved != 3 && report_bad_reserved_bits) fprint_err("!!! PMT: reserved (after zero bit) is %d, not 3\n",reserved); section_length = ((data[1] & 0xF) << 8) | data[2]; if (verbose) fprint_msg(" section length: %03x (%d)\n",section_length,section_length); // If the section length doesn't match our data length, we've got problems // (remember, the section_length counts bytes after the section_length field) if (section_length > data_len - 3) { fprint_err("### PMT section length %d is more than" " length of remaining data %d\n",section_length,data_len-3); return 1; } else if (section_length < data_len - 3) { fprint_err("!!! PMT section length %d does not use all of" " remaining data %d\n",section_length,data_len-3); // Adjust it and carry on data_len = section_length + 3; } program_number = (data[3] << 8) | data[4]; if (verbose) fprint_msg(" program number: %04x\n",program_number); reserved = (data[5] & 0xC0) >> 6; if (reserved != 3 && report_bad_reserved_bits) fprint_err("!!! PMT: reserved (after program_number)" " is %d, not 3\n",reserved); version_number = (data[5] & 0x3E) >> 1; current_next_indicator = data[5] & 0x1; section_number = data[6]; last_section_number = data[7]; if (verbose) fprint_msg(" version number %02x, current next %x, section number %x, last" " section number %x\n",version_number,current_next_indicator, section_number,last_section_number); reserved = (data[8] & 0xE0) >> 5; if (reserved != 7 && report_bad_reserved_bits) fprint_err("!!! PMT: reserved (after last_section_number)" " is %d, not 7\n",reserved); pcr_pid = ((data[8] & 0x1F) << 8) | data[9]; if (verbose) fprint_msg(" PCR PID: %04x\n",pcr_pid); reserved = (data[10] & 0xF0) >> 4; if (reserved != 0xF && report_bad_reserved_bits) fprint_err("!!! PMT: reserved (after PCR PID)" " is %x, not F\n",reserved); program_info_length = ((data[10] & 0x0F) << 8) | data[11]; if (verbose) fprint_msg(" program info length: %d\n",program_info_length); if (verbose && program_info_length > 0) { print_msg(" Program info:\n"); print_descriptors(TRUE," ",NULL,&data[12],program_info_length); } // 32 bits at the end of a program association section is reserved for a CRC // (OK, let's extract it stupidly...) crc = (crc << 8) | data[data_len-4]; crc = (crc << 8) | data[data_len-3]; crc = (crc << 8) | data[data_len-2]; crc = (crc << 8) | data[data_len-1]; // Let's check the CRC check_crc = crc32_block(0xffffffff,data,data_len); if (check_crc != 0) { fprint_err("!!! Calculated CRC for PMT (PID %04x) is %08x, not 00000000" " (CRC in data was %08x)\n",pid,check_crc,crc); // Should we carry on or give up (if "give up", then "!!!" should be "###"). //return 1; } // So we can work out the length of the actual program data // (remember the section length is for the bytes *after* the section // length field, so for data[3...]) stream_data = data + 12 + program_info_length; stream_data_len = data_len - 12 - program_info_length - 4; // "-4" == CRC //print_data(TRUE,"Rest:",stream_data,stream_data_len,1000); *pmt = build_pmt(program_number,version_number,pcr_pid); if (*pmt == NULL) return 1; if (program_info_length > 0) { err = set_pmt_program_info(*pmt,program_info_length,&data[12]); if (err) { free_pmt(pmt); return 1; } } if (verbose) print_msg(" Program streams:\n"); while (stream_data_len > 0) { int stream_type = stream_data[0]; uint32_t pid = ((stream_data[1] & 0x1F) << 8) | stream_data[2]; int ES_info_length = ((stream_data[3] & 0x0F) << 8) | stream_data[4]; if (verbose) { fprint_msg(" PID %04x -> Stream %02x %s\n",pid,stream_type, h222_stream_type_str(stream_type)); if (ES_info_length > 0) print_descriptors(TRUE," ",NULL,&stream_data[5],ES_info_length); } err = add_stream_to_pmt(*pmt,pid,stream_type,ES_info_length, stream_data+5); if (err) { free_pmt(pmt); return 1; } stream_data = stream_data + 5 + ES_info_length; stream_data_len = stream_data_len - 5 - ES_info_length; } return 0; } /* * Extract the stream list (and PCR PID) from a PMT packet. * * Assumes that the whole content of the PMT is in this single packet. * * - if `verbose`, then report on what we're doing * - `payload` is the payload of the TS packet. We know it can't be more * than 184 bytes long, because of the packet header bytes. * - regardless, `payload_len` is the actual length of the payload. * - `pid` is the PID of this TS packet. * - `program_number` is the program number. * - `pcr_pid` is the PID of packets containing the PCR, or 0. * - `stream_list` is a list of stream versus PID. * * Returns 0 if all went well, 1 if something went wrong. */ extern int extract_stream_list_from_pmt(int verbose, byte payload[MAX_TS_PAYLOAD_SIZE], int payload_len, uint32_t pid, int *program_number, uint32_t *pcr_pid, pidint_list_p *stream_list) { byte *data; int data_len; int pointer; int table_id; int section_syntax_indicator,zero_bit,reserved; int section_length; int version_number; int current_next_indicator; int section_number; int last_section_number; int program_info_length; uint32_t crc = 0; uint32_t check_crc; byte *stream_data; int stream_data_len; int err; if (payload_len == 0) { print_err("### PMT payload has zero length\n"); return 1; } if (payload == NULL) { print_err("### PMT payload is NULL\n"); return 1; } pointer = payload[0]; if (pointer > (payload_len - 1)) { fprint_err("### PMT payload: pointer is %d, which is off the end of" " the packet (length %d)\n",pointer,payload_len); return 1; } // if (DEBUG) print_data(TRUE,"PMT",payload,payload_len,1000); data = payload + pointer + 1; data_len = payload_len - pointer - 1; if (DEBUG) print_data(TRUE,"Data",data,data_len,1000); // Check the table id (maybe this should be done by our caller?) table_id = data[0]; if (table_id != 2) { // The table_id for a PMT is 2. // A PAT may also reference user private tables, and I've seen data with // other table values (including FF) as well: if (0x03 <= table_id && table_id <=0xFE) // user private table { if (verbose) { fprint_msg(" 'PMT' with PID %04x is user private table %02x\n",pid,table_id); print_data(TRUE," Data",data,data_len,20); } } else { if (0x03 <= table_id && table_id <= 0x3F) fprint_err("### PMT table id is %0#x (H.222 / ISO/IEC 13818-1" " reserved), should be 2\n",table_id); else fprint_err("### PMT table id is %0#x (%s), should be 2\n", table_id,(table_id==0x00?"PAT": table_id==0x01?"CAT": table_id==0xFF?"Forbidden":"???")); print_data(FALSE," Data",data,data_len,20); } // Best we can do is to pretend it didn't happen *program_number = 0; *pcr_pid = 0; *stream_list = NULL; return 0; } // Check bits section_syntax_indicator = (data[1] & 0x80) >> 7; zero_bit = (data[1] & 0x40) >> 6; reserved = (data[1] & 0x30) >> 4; if (section_syntax_indicator != 1 && report_bad_reserved_bits) print_err("!!! PMT: section syntax indicator is 0, not 1\n"); if (zero_bit != 0 && report_bad_reserved_bits) print_err("!!! PMT: zero bit is 1, not 0\n"); if (reserved != 3 && report_bad_reserved_bits) fprint_err("!!! PMT: reserved (after zero bit) is %d, not 3\n",reserved); section_length = ((data[1] & 0xF) << 8) | data[2]; if (verbose) fprint_msg(" section length: %03x (%d)\n",section_length,section_length); // If the section length continues into another packet, we're not going // to cope with it. Otherwise, we need to adjust our idea of how long // the data we want to "read" is. if (section_length + 3 > data_len) { fprint_err("### PMT continues into another packet - section length %d," " remaining packet data length %d\n", section_length,data_len-3); fprint_err(" This software does not support PMT data spanning" " multiple TS packets\n"); return 1; } data_len = section_length + 3; *program_number = (data[3] << 8) | data[4]; if (verbose) fprint_msg(" program number: %04x\n",*program_number); reserved = (data[5] & 0xC0) >> 6; if (reserved != 3 && report_bad_reserved_bits) fprint_err("!!! PMT: reserved (after program_number)" " is %d, not 3\n",reserved); version_number = (data[5] & 0x3E) >> 1; current_next_indicator = data[5] & 0x1; section_number = data[6]; last_section_number = data[7]; if (verbose) fprint_msg(" version number %02x, current next %x, section number %x, last" " section number %x\n",version_number,current_next_indicator, section_number,last_section_number); reserved = (data[8] & 0xE0) >> 5; if (reserved != 7 && report_bad_reserved_bits) fprint_err("!!! PMT: reserved (after last_section_number)" " is %d, not 7\n",reserved); *pcr_pid = ((data[8] & 0x1F) << 8) | data[9]; if (verbose) fprint_msg(" PCR PID: %04x\n",*pcr_pid); reserved = (data[10] & 0xF0) >> 4; if (reserved != 0xF && report_bad_reserved_bits) fprint_err("!!! PMT: reserved (after PCR PID)" " is %x, not F\n",reserved); program_info_length = ((data[10] & 0x0F) << 8) | data[11]; if (verbose) fprint_msg(" program info length: %d\n",program_info_length); if (verbose && program_info_length > 0) { print_msg(" Program info:\n"); print_descriptors(TRUE," ",NULL,&data[12],program_info_length); } // 32 bits at the end of a program association section is reserved for a CRC // (OK, let's extract it stupidly...) crc = (crc << 8) | data[data_len-4]; crc = (crc << 8) | data[data_len-3]; crc = (crc << 8) | data[data_len-2]; crc = (crc << 8) | data[data_len-1]; // Let's check the CRC check_crc = crc32_block(0xffffffff,data,data_len); if (check_crc != 0) { fprint_err("!!! Calculated CRC for PMT (PID %04x) is %08x, not 00000000" " (CRC in data was %08x)\n",pid,check_crc,crc); return 1; } // So we can work out the length of the actual program data // (remember the section length is for the bytes *after* the section // length field, so for data[3...]) stream_data = data + 12 + program_info_length; stream_data_len = data_len - 12 - program_info_length - 4; // "-4" == CRC //print_data(TRUE,"Rest:",stream_data,stream_data_len,1000); err = build_pidint_list(stream_list); if (err) return 1; if (verbose) print_msg(" Program streams:\n"); while (stream_data_len > 0) { int stream_type = stream_data[0]; uint32_t pid = ((stream_data[1] & 0x1F) << 8) | stream_data[2]; int ES_info_length = ((stream_data[3] & 0x0F) << 8) | stream_data[4]; if (verbose) { #define SARRAYSIZE 40 char buf[SARRAYSIZE]; snprintf(buf,SARRAYSIZE,"(%s)",h222_stream_type_str(stream_type)); // On Windows, snprintf does not guarantee to write a terminating NULL buf[SARRAYSIZE-1] = '\0'; fprint_msg(" Stream %02x %-40s -> PID %04x\n",stream_type,buf,pid); if (ES_info_length > 0) print_descriptors(TRUE," ",NULL,&stream_data[5],ES_info_length); } // For the moment, we shan't bother to remember the extra info. err = append_to_pidint_list(*stream_list,pid,stream_type); if (err) return 1; stream_data = stream_data + 5 + ES_info_length; stream_data_len = stream_data_len - 5 - ES_info_length; } return 0; } /* * Split a TS packet into its main parts * * - `buf` is the data for the packet * - `pid` is the PID of said data * - `payload_unit_start_indicator` is TRUE if any payload in this * packet forms the start of a PES packet. Its meaning is not significant * if there is no payload, or if the payload is not (part of) a PES packet. * - `adapt` is an offset into `buf`, acting as an array of the actual * adaptation control bytes. It will be NULL if there are no adaptation * controls. * - `adapt_len` is the length of the adaptation controls (i.e., the * number of bytes). It will be 0 if there are no adaptation controls. * - `payload` is an offset into `buf`, acting as an array of the actual * payload bytes. It will be NULL if there is no payload. * - `payload_len` is the length of the payload *in this packet* (i.e., the * number of bytes. It will be 0 if there is no payload. * * Returns 0 if all went well, 1 if something went wrong. */ extern int split_TS_packet(byte buf[TS_PACKET_SIZE], uint32_t *pid, int *payload_unit_start_indicator, byte *adapt[], int *adapt_len, byte *payload[], int *payload_len) { int adaptation_field_control; if (buf[0] != 0x47) { fprint_err("### TS packet starts %02x, not %02x\n",buf[0],0x47); return 1; } *payload_unit_start_indicator = (buf[1] & 0x40) >> 6; *pid = ((buf[1] & 0x1f) << 8) | buf[2]; if (*pid == 0x1FFF) { // Null packets don't contain any data, so let's not allow "spurious" // interpretation of their innards *adapt = NULL; *adapt_len = 0; *payload = NULL; *payload_len = 0; return 0; } adaptation_field_control = (buf[3] & 0x30) >> 4; switch (adaptation_field_control) { case 0: fprint_err("### Packet PID %04x has adaptation field control = 0\n" " which is a reserved value (no payload, no adaptation field)\n", *pid); *adapt = NULL; *adapt_len = 0; *payload = NULL; *payload_len = 0; break; case 1: // Payload only *adapt = NULL; *adapt_len = 0; *payload = buf + 4; *payload_len = TS_PACKET_SIZE - 4; break; case 2: // Adaptation field only *adapt_len = buf[4]; if (*adapt_len == 0) *adapt = NULL; else *adapt = buf + 5; *payload = NULL; *payload_len = 0; break; case 3: // Payload and adaptation field *adapt_len = buf[4]; if (*adapt_len == 0) *adapt = NULL; else *adapt = buf + 5; *payload = buf + 5 + buf[4]; *payload_len = TS_PACKET_SIZE - 5 - buf[4]; break; default: // How this might occur, other than via program error, I can't think. fprint_err("### Packet PID %04x has adaptation field control %x\n", *pid,adaptation_field_control); return 1; } return 0; } /* * Return the next TS packet, as payload and adaptation controls. * * This is a convenience wrapping of `read_next_TS_packet` and * `split_TS_packet`. Because of this, the data referenced by `adapt` and * `payload` will generally not persist over further calls of this function * and `read_next_TS_packet`, as it is held within the TS reader's read-ahead * buffer. * * - `tsreader` is the TS packet reading context * - `pid` is the PID of said data * - `payload_unit_start_indicator` is TRUE if any payload in this * packet forms the start of a PES packet. Its meaning is not significant * if there is no payload, or if the payload is not (part of) a PES packet. * - `adapt` is an offset into `buf`, acting as an array of the actual * adaptation control bytes. It will be NULL if there are no adaptation * controls. * - `adapt_len` is the length of the adaptation controls (i.e., the * number of bytes). It will be 0 if there are no adaptation controls. * - `payload` is an offset into `buf`, acting as an array of the actual * payload bytes. It will be NULL if there is no payload. * - `payload_len` is the length of the payload *in this packet* (i.e., the * number of bytes. It will be 0 if there is no payload. * * Returns 0 if all went well, EOF if there is no more data, 1 if something * went wrong. */ extern int get_next_TS_packet(TS_reader_p tsreader, uint32_t *pid, int *payload_unit_start_indicator, byte *adapt[], int *adapt_len, byte *payload[], int *payload_len) { int err; byte *packet; err = read_next_TS_packet(tsreader,&packet); if (err == EOF) return EOF; else if (err) { print_err("### Error reading TS packet\n"); return 1; } return split_TS_packet(packet,pid,payload_unit_start_indicator, adapt,adapt_len,payload,payload_len); } /* * Find the first (next) PAT. * * - `tsreader` is the TS packet reading context * - if `max` is non-zero, then it is the maximum number of TS packets to read * - if `verbose` is true, then output extra information * - if `quiet` is true, then don't output normal informational messages * - `num_read` is the number of packets read to find the PAT (or before * giving up) * - `prog_list` is the program list from the PAT, or NULL if none was found * * Returns 0 if all went well, EOF if no PAT was found, * 1 if something else went wrong. */ extern int find_pat(TS_reader_p tsreader, int max, int verbose, int quiet, int *num_read, pidint_list_p *prog_list) { int err; byte *pat_data = NULL; int pat_data_len = 0; int pat_data_used = 0; *prog_list = NULL; *num_read = 0; if (!quiet) print_msg("Locating first PAT\n"); for (;;) { uint32_t pid; int payload_unit_start_indicator; byte *adapt, *payload; int adapt_len, payload_len; err = get_next_TS_packet(tsreader,&pid, &payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err == EOF) return EOF; else if (err) { print_err("### Error reading TS packet\n"); if (pat_data) free(pat_data); return 1; } (*num_read) ++; if (pid == 0x0000) { if (!quiet) fprint_msg("Found PAT after reading %d packet%s\n", *num_read,(*num_read==1?"":"s")); if (payload_len == 0) { print_err("### Packet is PAT, but has no payload\n"); if (pat_data) free(pat_data); return 1; } if (payload_unit_start_indicator && pat_data) { // Lose any data we started but didn't complete print_err("!!! Discarding previous (uncompleted) PAT data\n"); free(pat_data); pat_data = NULL; pat_data_len = 0; pat_data_used = 0; } else if (!payload_unit_start_indicator && !pat_data) { print_err("!!! Discarding PAT continuation, no PAT started\n"); continue; } err = build_psi_data(verbose,payload,payload_len,pid, &pat_data,&pat_data_len,&pat_data_used); if (err) { fprint_err("### Error %s PAT\n", (payload_unit_start_indicator?"starting new":"continuing")); if (pat_data) free(pat_data); return 1; } if (pat_data_len == pat_data_used) { err = extract_prog_list_from_pat(verbose,pat_data,pat_data_len,prog_list); if (pat_data) free(pat_data); return err; } } if (max > 0 && *num_read >= max) { if (!quiet) fprint_msg("Stopping after %d TS packets\n",max); if (pat_data) free(pat_data); return EOF; } } } /* * Find the next PMT, and report on it. * * - `tsreader` is the TS packet reading context * - `pmt_pid` is the PID of the PMT we are looking for * - if `program_number` is -1, then any PMT with that PID is acceptable, * otherwise we're only interested in a PMT with that PID and the given * program number. * - if `max` is non-zero, then it is the maximum number of TS packets to read * - if `verbose` is true, then output extra information * - if `quiet` is true, then don't output normal informational messages * - `num_read` is the number of packets read to find the PMT (or before * giving up) * - `pmt` is a new datastructure representing the PMT found * * Returns 0 if all went well, EOF if no PMT was found, * 1 if something else went wrong. */ extern int find_next_pmt(TS_reader_p tsreader, uint32_t pmt_pid, int program_number, int max, int verbose, int quiet, int *num_read, pmt_p *pmt) { int err; byte *pmt_data = NULL; int pmt_data_len = 0; int pmt_data_used = 0; *pmt = NULL; *num_read = 0; if (!quiet) print_msg("Locating next PMT\n"); for (;;) { uint32_t pid; int payload_unit_start_indicator; byte *adapt, *payload; int adapt_len, payload_len; err = get_next_TS_packet(tsreader,&pid, &payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err == EOF) { if (pmt_data) free(pmt_data); return EOF; } else if (err) { print_err("### Error reading TS packet\n"); if (pmt_data) free(pmt_data); return 1; } (*num_read) ++; if (pid == pmt_pid) { if (!quiet) fprint_msg("Found %s PMT with PID %04x (%d) after reading %d packet%s\n", (payload_unit_start_indicator?"start of":"more of"), pid,pid,*num_read,(*num_read==1?"":"s")); if (payload_len == 0) { fprint_err("### Packet is PMT with PID %04x (%d)," " but has no payload\n",pid,pid); if (pmt_data) free(pmt_data); return 1; } if (payload_unit_start_indicator && pmt_data) { // Lose any data we started but didn't complete print_err("!!! Discarding previous (uncompleted) PMT data\n"); free(pmt_data); pmt_data = NULL; pmt_data_len = 0; pmt_data_used = 0; } else if (!payload_unit_start_indicator && !pmt_data) { print_err("!!! Discarding PMT continuation, no PMT started\n"); continue; } err = build_psi_data(verbose,payload,payload_len,pid, &pmt_data,&pmt_data_len,&pmt_data_used); if (err) { fprint_err("### Error %s PMT\n", (payload_unit_start_indicator?"starting new":"continuing")); if (pmt_data) free(pmt_data); return 1; } if (pmt_data_len == pmt_data_used) { int pmt_program_number; err = extract_pmt(verbose,pmt_data,pmt_data_len,pid,pmt); pmt_program_number = *pmt == NULL ? -1 : (int)((*pmt)->program_number); if (pmt_data) { free(pmt_data); pmt_data = NULL; } // Check we've got the right program number - it would appear to be // legitimate to have multiple PMTs carried in the same PID and some // abuse of this appears to happen in real life if (err == 0 && program_number >= 0) { if (pmt_program_number != program_number) { fprint_err("!!! Discarding PMT with program number %d\n", pmt_program_number); free_pmt(pmt); continue; } } return err; } } if (max > 0 && *num_read >= max) { if (!quiet) fprint_msg("Stopping after %d TS packets\n",max); if (pmt_data) free(pmt_data); return EOF; } } } /* * Find the next PAT, and from that the next PMT. * * Looks for the next PAT in the input stream, and then for the first * PMT thereafter. If there is more than one program stream in the PAT, * it looks for the PMT for the first. * * - `tsreader` is the TS packet reading context * - if `max` is non-zero, then it is the maximum number of TS packets to read * - if `verbose` is true, then output extra information * - if `quiet` is true, then don't output normal informational messages * - `num_read` is the number of packets read to find the PMT (or before * giving up) * - `pmt` is a new datastructure containing the information from the PMT. * * Returns 0 if all went well, EOF if no PAT or PMT was found (and thus * no program stream), -2 if a PAT was found but it did not contain any * programs, 1 if something else went wrong. */ extern int find_pmt(TS_reader_p tsreader, const int req_prog_no, int max, int verbose, int quiet, int *num_read, pmt_p *pmt) { int err; pidint_list_p prog_list = NULL; int sofar; int prog_index = 0; int prog_no = 0; *pmt = NULL; err = find_pat(tsreader,max,verbose,quiet,&sofar,&prog_list); if (err == EOF) { if (!quiet) print_msg("No PAT found\n"); return 1; } else if (err) { print_err("### Error finding PAT\n"); return 1; } if (!quiet) { print_msg("\n"); report_pidint_list(prog_list,"Program list","Program",FALSE); print_msg("\n"); } if (prog_list->length == 0) { if (!quiet) fprint_msg("No programs defined in PAT (packet %d)\n",sofar); return -2; } else if (prog_list->length > 1 && !quiet) { if (req_prog_no == 1) print_msg("Multiple programs in PAT - using the first non-zero\n\n"); else fprint_msg("Multiple programs in PAT - program %d\n\n", req_prog_no); } for (prog_index = 0; prog_index < prog_list->length; ++prog_index) { if (prog_list->number[prog_index] == 0) continue; if (++prog_no == req_prog_no) break; } if (prog_no == 0) { fprint_msg("No non-zero program_numbers in PAT (packet %d)\n",sofar); return -2; } if (prog_no != req_prog_no) { fprint_msg("Unable to find program %d in PAT, only found %d (packet %d)\n", req_prog_no, prog_no, sofar); return -2; } // Amend max to take account of the packets we've already read max -= sofar; err = find_next_pmt(tsreader,prog_list->pid[prog_index],prog_list->number[prog_index], max,verbose,quiet,num_read,pmt); free_pidint_list(&prog_list); *num_read += sofar; if (err == EOF) { if (!quiet) print_msg("No PMT found\n"); return EOF; } else if (err) { print_err("### Error finding PMT\n"); return 1; } if (!quiet) { print_msg("\n"); print_msg("Program map\n"); report_pmt(TRUE," ",*pmt); print_msg("\n"); } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ts2es.c000066400000000000000000000461631261471605300161060ustar00rootroot00000000000000/* * Given an H.222 transport stream (TS) file, extract elementary stream * data therefrom (i.e., extract the ES from the PES packets within the * TS). * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ts_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "pidint_fns.h" #include "es_fns.h" #include "pes_fns.h" #include "version.h" // A three-way choice for what to output by PID enum pid_extract { EXTRACT_UNDEFINED, EXTRACT_VIDEO, // Output the first "named" video stream EXTRACT_AUDIO, // Ditto for audio EXTRACT_PID, // Output an explicit PID }; typedef enum pid_extract EXTRACT; /* * Extract all the TS packets for either a video or audio stream. * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_av_via_pes(char *input_name, char *output_name, int want_video, int quiet) { int err; PES_reader_p reader = NULL; ES_p es = NULL; FILE *output; if (!want_video) { print_err("### Audio output is not supported via PES in this utility\n"); return 1; } output = fopen(output_name,"wb"); if (output == NULL) { fprint_err("### Unable to open output file %s: %s\n",output_name, strerror(errno)); return 1; } err = open_PES_reader(input_name,!quiet,!quiet,&reader); if (err) { fprint_err("### Error opening file %s\n",input_name); fclose(output); return 1; } set_PES_reader_video_only(reader,TRUE); // Wrap our PES stream up as an ES stream err = build_elementary_stream_PES(reader,&es); if (err) { print_err("### Error trying to build ES reader from PES reader\n"); (void) close_PES_reader(&reader); (void) fclose(output); return 1; } for (;;) { ES_unit_p unit; err = find_and_build_next_ES_unit(es,&unit); if (err == EOF) break; else if (err) { print_err("### Error reading next ES unit\n"); (void) fclose(output); (void) close_PES_reader(&reader); close_elementary_stream(&es); return 1; } err = write_ES_unit(output,unit); if (err) { print_err("### Error writing ES unit out to file\n"); free_ES_unit(&unit); (void) fclose(output); (void) close_PES_reader(&reader); close_elementary_stream(&es); return 1; } free_ES_unit(&unit); } (void) fclose(output); // naughtily ignore the return code (void) close_PES_reader(&reader); // naughtily ignore the return code close_elementary_stream(&es); return 0; } /* * Extract all the TS packets for a nominated PID to another file. * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_pid_packets(TS_reader_p tsreader, FILE *output, uint32_t pid_wanted, int max, int verbose, int quiet) { int err; int count = 0; int extracted = 0; int pes_packet_len = 0; int got_pes_packet_len = FALSE; // It doesn't make sense to start outputting data for our PID until we // get the start of a packet int need_packet_start = TRUE; for (;;) { uint32_t pid; int payload_unit_start_indicator; byte *adapt, *payload; int adapt_len, payload_len; if (max > 0 && count >= max) { if (!quiet) fprint_msg("Stopping after %d packets\n",max); break; } err = get_next_TS_packet(tsreader,&pid, &payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err == EOF) break; else if (err) { print_err("### Error reading TS packet\n"); return 1; } count++; // If the packet is empty, all we can do is ignore it if (payload_len == 0) continue; if (pid == pid_wanted) { byte *data; int data_len; size_t written; if (verbose) { fprint_msg("%4d: TS Packet PID %04x",count,pid); if (payload_unit_start_indicator) print_msg(" (start)"); else if (need_packet_start) print_msg(" "); print_msg("\n"); } if (payload_unit_start_indicator) { // It's the start of a PES packet, so we need to drop the header int offset; if (need_packet_start) need_packet_start = FALSE; pes_packet_len = (payload[4] << 8) | payload[5]; if (verbose) fprint_msg("PES packet length %d\n",pes_packet_len); got_pes_packet_len = (pes_packet_len > 0); if (IS_H222_PES(payload)) { // It's H.222.0 - payload[8] is the PES_header_data_length, // so our ES data starts that many bytes after that field offset = payload[8] + 9; } else { // We assume it's MPEG-1 offset = calc_mpeg1_pes_offset(payload,payload_len); } data = &payload[offset]; data_len = payload_len-offset; if (verbose) print_data(TRUE,"data",data,data_len,1000); } else { // If we haven't *started* a packet, we can't use this, // since it will just look like random bytes when written out. if (need_packet_start) { continue; } data = payload; data_len = payload_len; if (verbose) print_data(TRUE,"Data",payload,payload_len,1000); if (got_pes_packet_len) { // Try not to write more data than the PES packet declares if (data_len > pes_packet_len) { data_len = pes_packet_len; if (verbose) print_data(TRUE,"Reduced data",data,data_len,1000); pes_packet_len = 0; } else pes_packet_len -= data_len; } } if (data_len > 0) { // Windows doesn't seem to like writing 0 bytes, so be careful... written = fwrite(data,data_len,1,output); if (written != 1) { fprint_err("### Error writing TS packet - units written = %d\n", (int)written); return 1; } } extracted ++; } } if (!quiet) fprint_msg("Extracted %d of %d TS packet%s\n", extracted,count,(count==1?"":"s")); // If the user has forgotten to say -pid XX, or -video/-audio, // and are piping the output to another program, it can be surprising // if there is no data! if (quiet && extracted == 0) fprint_err("### No data extracted for PID %#04x (%d)\n", pid_wanted,pid_wanted); return 0; } /* * Extract all the TS packets for either a video or audio stream. * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_av(int input, FILE *output, int want_video, int max, int verbose, int quiet) { int err, ii; int max_to_read = max; int total_num_read = 0; uint32_t pid = 0; TS_reader_p tsreader = NULL; pmt_p pmt = NULL; // Turn our file into a TS reader err = build_TS_reader(input,&tsreader); if (err) return 1; // First, find out what program streams we actually have for (;;) { int num_read; // Give up if we've read more than our limit if (max > 0 && max_to_read <= 0) break; err = find_pmt(tsreader, 1, max_to_read,verbose,quiet,&num_read,&pmt); if (err == EOF) { if (!quiet) print_msg("No program stream information in the input file\n"); free_TS_reader(&tsreader); free_pmt(&pmt); return 0; } else if (err) { print_err("### Error finding program stream information\n"); free_TS_reader(&tsreader); free_pmt(&pmt); return 1; } max_to_read -= num_read; total_num_read += num_read; // From that, find a stream of the type we want... // Note that the audio detection will accept either DVB or ADTS Dolby (AC-3) // stream types for (ii=0; ii < pmt->num_streams; ii++) { if (( want_video && IS_VIDEO_STREAM_TYPE(pmt->streams[ii].stream_type)) || (!want_video && (IS_AUDIO_STREAM_TYPE(pmt->streams[ii].stream_type)))) { pid = pmt->streams[ii].elementary_PID; break; } } free_pmt(&pmt); // Did we find what we want? If not, go round again and look for the // next PMT (subject to the number of records we're willing to search) if (pid != 0) break; } if (pid == 0) { fprint_err("### No %s stream specified in first %d TS packets in input file\n", (want_video?"video":"audio"),max); free_TS_reader(&tsreader); return 1; } if (!quiet) fprint_msg("Extracting %s PID %04x (%d)\n",(want_video?"video":"audio"), pid,pid); // Amend max to take account of the packets we've already read max -= total_num_read; // And do the extraction. err = extract_pid_packets(tsreader,output,pid,max,verbose,quiet); free_TS_reader(&tsreader); return err; } /* * Extract all the TS packets for a nominated PID to another file. * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_pid(int input, FILE *output, uint32_t pid_wanted, int max, int verbose, int quiet) { int err; TS_reader_p tsreader = NULL; // Turn our file into a TS reader err = build_TS_reader(input,&tsreader); if (err) return 1; err = extract_pid_packets(tsreader,output,pid_wanted,max,verbose,quiet); free_TS_reader(&tsreader); return err; } static void print_usage() { print_msg( "Usage: ts2es [switches] [] []\n" "\n" ); REPORT_VERSION("ts2es"); print_msg( "\n" " Extract a single (elementary) program stream from a Transport Stream\n" " (or Program Stream).\n" "\n" "Files:\n" " is an H.222 Transport Stream file (but see -stdin and -pes)\n" " is a single elementary stream file (but see -stdout)\n" "\n" "Which stream to extract:\n" " -pid Output data for the stream with the given\n" " . Use -pid 0x to specify a hex value\n" " -video Output data for the (first) video stream\n" " named in the (first) PMT. This is the default.\n" " -audio Output data for the (first) audio stream\n" " named in the (first) PMT\n" "\n" "General switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Input from standard input, instead of a file\n" " -stdout Output to standard output, instead of a file\n" " Forces -quiet and -err stderr.\n" " -verbose, -v Output informational/diagnostic messages\n" " -quiet, -q Only output error messages\n" " -max , -m Maximum number of TS packets to read\n" "\n" " -pes, -ps Use the PES interface to read ES units from\n" " the input file. This allows PS data to be read\n" " (there is no point in using this for TS data).\n" " Does not support -pid, -stdin or -stdout.\n" ); } int main(int argc, char **argv) { int use_stdout = FALSE; int use_stdin = FALSE; char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; char *action_switch = "None"; EXTRACT extract = EXTRACT_VIDEO; // What we're meant to extract int input = -1; // Our input file descriptor FILE *output = NULL; // The stream we're writing to (if any) int max = 0; // The maximum number of TS packets to read (or 0) uint32_t pid = 0; // The PID of the (single) stream to extract int quiet = FALSE; // True => be as quiet as possible int verbose = FALSE; // True => output diagnostic/progress messages int use_pes = FALSE; int err = 0; int ii = 1; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("ts2es",ii); err = int_value("ts2es",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-pes",argv[ii]) || !strcmp("-ps",argv[ii])) { use_pes = TRUE; } else if (!strcmp("-pid",argv[ii])) { CHECKARG("ts2es",ii); err = unsigned_value("ts2es",argv[ii],argv[ii+1],0,&pid); if (err) return 1; ii++; extract = EXTRACT_PID; } else if (!strcmp("-video",argv[ii])) { extract = EXTRACT_VIDEO; } else if (!strcmp("-audio",argv[ii])) { extract = EXTRACT_AUDIO; } else if (!strcmp("-stdin",argv[ii])) { use_stdin = TRUE; had_input_name = TRUE; // so to speak } else if (!strcmp("-stdout",argv[ii])) { use_stdout = TRUE; had_output_name = TRUE; // so to speak redirect_output_stderr(); } else if (!strcmp("-err",argv[ii])) { CHECKARG("ts2es",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### ts2es: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else { fprint_err("### ts2es: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name && had_output_name) { fprint_err("### ts2es: Unexpected '%s'\n",argv[ii]); return 1; } else if (had_input_name) // shouldn't do this if had -stdout { output_name = argv[ii]; had_output_name = TRUE; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### ts2es: No input file specified\n"); return 1; } if (!had_output_name) { fprint_err("### ts2es: " "No output file specified for %s\n",action_switch); return 1; } // ============================================================ // Testing PES output if (use_pes && extract == EXTRACT_PID) { print_err("### ts2es: -pid is not supported with -pes\n"); return 1; } if (use_pes && use_stdout) { print_err("### ts2es: -stdout is not supported with -pes\n"); return 1; } if (use_pes && use_stdin) { print_err("### ts2es: -stdin is not supported with -pes\n"); return 1; } if (use_pes) { err = extract_av_via_pes(input_name,output_name,(extract==EXTRACT_VIDEO), quiet); if (err) { print_err("### ts2es: Error writing via PES\n"); return 1; } return 0; } // ============================================================ // Try to stop extraneous data ending up in our output stream if (use_stdout) { verbose = FALSE; quiet = TRUE; } if (use_stdin) input = STDIN_FILENO; else { input = open_binary_file(input_name,FALSE); if (input == -1) { fprint_err("### ts2es: Unable to open input file %s\n",input_name); return 1; } } if (!quiet) fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); if (had_output_name) { if (use_stdout) output = stdout; else { output = fopen(output_name,"wb"); if (output == NULL) { if (!use_stdin) (void) close_file(input); fprint_err("### ts2es: " "Unable to open output file %s: %s\n",output_name, strerror(errno)); return 1; } } if (!quiet) fprint_msg("Writing to %s\n",(use_stdout?"":output_name)); } if (!quiet) { if (extract == EXTRACT_PID) fprint_msg("Extracting packets for PID %04x (%d)\n",pid,pid); else fprint_msg("Extracting %s\n",(extract==EXTRACT_VIDEO?"video":"audio")); } if (max && !quiet) fprint_msg("Stopping after %d TS packets\n",max); if (extract == EXTRACT_PID) err = extract_pid(input,output,pid,max,verbose,quiet); else err = extract_av(input,output,(extract==EXTRACT_VIDEO), max,verbose,quiet); if (err) { print_err("### ts2es: Error extracting data\n"); if (!use_stdin) (void) close_file(input); if (!use_stdout) (void) fclose(output); return 1; } // And tidy up when we're finished if (!use_stdout) { errno = 0; err = fclose(output); if (err) { fprint_err("### ts2es: Error closing output file %s: %s\n", output_name,strerror(errno)); (void) close_file(input); return 1; } } if (!use_stdin) { err = close_file(input); if (err) fprint_err("### ts2es: Error closing input file %s\n",input_name); } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ts2ps.c000066400000000000000000000351571261471605300161220ustar00rootroot00000000000000/* * Given an H.222 transport stream (TS) file, extract PES data therefrom (i.e., * extract the PES packets within the TS) and construct PS. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ps_fns.h" #include "ts_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "pidint_fns.h" #include "pes_fns.h" #include "version.h" /* * Write a PS program end code * * Returns 0 if all went well, 1 if something went wrong */ static int write_program_end_code(FILE *output) { static byte program_end_code[] = {0x00, 0x00, 0x01, 0xB9}; size_t count = fwrite(program_end_code,4,1,output); if (count != 1) { print_err("### Error writing PS program end code\n"); return 1; } return 0; } /* * Write a PS pack header. * * Returns 0 if all went well, 1 if something went wrong */ static int write_pack_header(FILE *output) { static byte pack_header[] = {0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x00, 0x00, 0x03, 0xF8}; size_t count; // For the moment, just write out an "unset" pack header // This is illegal because the mux rate is zero (the standard // specifically forbids that) count = fwrite(pack_header,sizeof(pack_header),1,output); if (count != 1) { print_err("### Error writing PS pack header out to file\n"); return 1; } return 0; } /* * Write a PES packet from the given data * * `data_len` must be at most 0xFFFF - 3, allowing for the 3 bytes of the * PES header flags/header data length which we must output. */ static int write_PES_packet(FILE *output, byte *data, uint16_t data_len, byte stream_id) { static byte header[] = {0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, // replace 3 bytes 0x80, 0x00, 0x00}; // flags and header data len size_t count; uint16_t PES_packet_length = data_len + 3; // + 3 for the flags, etc. header[3] = stream_id; header[4] = (PES_packet_length & 0xFF00) >> 8; header[5] = (PES_packet_length & 0x00FF); count = fwrite(header,sizeof(header),1,output); if (count != 1) { print_err("### Error writing PS PES packet header out to file\n"); return 1; } count = fwrite(data,data_len,1,output); if (count != 1) { print_err("### Error writing PS PES packet data out to file\n"); return 1; } return 0; } /* * Extract data and output it as PS * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_data(int input, FILE *output, uint16_t program_number, int max, int verbose, int quiet) { int err; PES_reader_p reader; err = build_PES_reader(input,TRUE,!quiet,!quiet,program_number,&reader); if (err) { print_err("### Error building PES reader over input file\n"); return 1; } // Temporarily, just writes out PES packets, not a PS stream... for (;;) { size_t count; err = read_next_PES_packet(reader); if (err == EOF) break; else if (err) { print_err("### Error reading next PES packet\n"); (void) free_PES_reader(&reader); return 1; } err = write_pack_header(output); if (err) { print_err("### Error writing PS pack header\n"); (void) free_PES_reader(&reader); return 1; } // It is possible that the TS data for video might have specified a zero // length in the PES. Our TS reader will have read all of the packet for // us, but will not have "adjusted" said length at the start of the packet. // It is thus up to us to catch this case and amend it before we output // the data... if (reader->packet->data[4] == 0 && reader->packet->data[5] == 0) { int32_t PES_packet_length = reader->packet->data_len - 6; byte *start = reader->packet->data; // Our maximum length is determined by the maximum length we can // indicate in the two bytes of the PES_packet_length. When we're // *writing* data, we also have to allow for writing the two flag // bytes and PES_header_data_length that come thereafter. #define MAX_LENGTH 0xFFFF if (PES_packet_length > MAX_LENGTH) { fprint_err("PES packet of 'zero' length is really %6d - too long for one packet\n", PES_packet_length); // Output what we can of the original packet reader->packet->data[4] = (MAX_LENGTH & 0xFF00) >> 8; reader->packet->data[5] = (MAX_LENGTH & 0x00FF); // Remember that we also write out the 6 bytes preceding those // MAX_LENGTH bytes... fprint_err(".. writing out %5d (%5d total)\n",MAX_LENGTH,MAX_LENGTH+6); count = fwrite(reader->packet->data,MAX_LENGTH+6,1,output); if (count != 1) { print_err("### Error writing (start of) PES packet out to file\n"); (void) free_PES_reader(&reader); return 1; } PES_packet_length -= MAX_LENGTH; start += MAX_LENGTH+6; while (PES_packet_length > 0) { // Now, when writing out chunks of data as PES packets, // we have 6 bytes of header (00 00 01 stream_id length/length) // followed by two bytes of flags (81 00) and a zero // PES_header_data_length (00). Those last three bytes have // to be included in the PES_packet_length of the PES packet // we write out, which means that the longest "chunk" of data // we can write is three less than the (otherwise) maximum. int this_length = min(MAX_LENGTH-3,PES_packet_length); int err; fprint_err(".. writing out %5d\n",this_length); err = write_PES_packet(output,start,this_length, reader->packet->data[3]); if (err) { print_err("### Error writing (part of) PES packet out to file\n"); (void) free_PES_reader(&reader); return 1; } PES_packet_length -= this_length; start += this_length; } } else { fprint_err("PES packet of 'zero' length, adjusting to %6d-6=%6d" " (stream id %02x, 'length' %d)\n", reader->packet->data_len,PES_packet_length, reader->packet->data[3],reader->packet->length); reader->packet->data[4] = (PES_packet_length & 0xFF00) >> 8; reader->packet->data[5] = (PES_packet_length & 0x00FF); count = fwrite(reader->packet->data,reader->packet->data_len,1,output); if (count != 1) { print_err("### Error writing PES packet out to file\n"); (void) free_PES_reader(&reader); return 1; } } } else { count = fwrite(reader->packet->data,reader->packet->data_len,1,output); if (count != 1) { print_err("### Error writing PES packet out to file\n"); (void) free_PES_reader(&reader); return 1; } } } err = write_program_end_code(output); if (err) { (void) free_PES_reader(&reader); return 1; } (void) free_PES_reader(&reader); // naughtily ignore the return code return 0; } static void print_usage() { print_msg( "Usage: ts2ps [switches] [] []\n" "\n" ); REPORT_VERSION("ts2ps"); print_msg( "\n" " Extract a single program stream from a Transport Stream.\n" "\n" " WARNING: This software does not yet generate legitimate PS data.\n" " In particular, the PS pack header records are illegal.\n" "\n" "Files:\n" " is an H.222 Transport Stream file (but see -stdin)\n" " is an H.222 Program Stream file (but see -stdout)\n" "\n" "General switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Input from standard input, instead of a file\n" " -stdout Output to standard output, instead of a file\n" " Forces -quiet and -err stderr.\n" " -verbose, -v Output informational/diagnostic messages\n" " -quiet, -q Only output error messages\n" " -max , -m Maximum number of TS packets to read\n" " (not currently used)\n" " -prog Choose program number (default 0, which means\n" " the first one found).\n" ); } int main(int argc, char **argv) { int use_stdout = FALSE; int use_stdin = FALSE; char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; int input = -1; // Our input file descriptor FILE *output = NULL; // The stream we're writing to (if any) int max = 0; // The maximum number of TS packets to read (or 0) int quiet = FALSE; // True => be as quiet as possible int verbose = FALSE; // True => output diagnostic/progress messages uint16_t program_number = 0; int err = 0; int ii = 1; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("ts2ps",ii); err = int_value("ts2ps",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-prog",argv[ii])) { int temp; CHECKARG("ts2ps",ii); err = int_value("ts2ps",argv[ii],argv[ii+1],TRUE,10,&temp); if (err) return 1; program_number = temp; ii++; } else if (!strcmp("-stdin",argv[ii])) { use_stdin = TRUE; had_input_name = TRUE; // so to speak } else if (!strcmp("-stdout",argv[ii])) { use_stdout = TRUE; had_output_name = TRUE; // so to speak redirect_output_stderr(); } else if (!strcmp("-err",argv[ii])) { CHECKARG("ts2ps",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### ts2ps: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else { fprint_err("### ts2ps: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name && had_output_name) { fprint_err("### ts2ps: Unexpected '%s'\n",argv[ii]); return 1; } else if (had_input_name) // shouldn't do this if had -stdout { output_name = argv[ii]; had_output_name = TRUE; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### ts2ps: No input file specified\n"); return 1; } if (!had_output_name) { print_err("### ts2ps: No output file specified\n"); return 1; } // Try to stop extraneous data ending up in our output stream if (use_stdout) { verbose = FALSE; quiet = TRUE; } if (use_stdin) input = STDIN_FILENO; else { input = open_binary_file(input_name,FALSE); if (input == -1) { fprint_err("### ts2ps: Unable to open input file %s\n",input_name); return 1; } } if (!quiet) fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); if (had_output_name) { if (use_stdout) output = stdout; else { output = fopen(output_name,"wb"); if (output == NULL) { if (!use_stdin) (void) close_file(input); fprint_err("### ts2ps: " "Unable to open output file %s: %s\n",output_name, strerror(errno)); return 1; } } if (!quiet) fprint_msg("Writing to %s\n",(use_stdout?"":output_name)); } if (max && !quiet) fprint_msg("Stopping after %d TS packets\n",max); err = extract_data(input,output,program_number,max,verbose,quiet); if (err) { print_err("### ts2ps: Error extracting data\n"); if (!use_stdin) (void) close_file(input); if (!use_stdout) (void) fclose(output); return 1; } // And tidy up when we're finished if (!use_stdout) { errno = 0; err = fclose(output); if (err) { fprint_err("### ts2ps: Error closing output file %s: %s\n", output_name,strerror(errno)); (void) close_file(input); return 1; } } if (!use_stdin) { err = close_file(input); if (err) fprint_err("### ts2ps: Error closing input file %s\n",input_name); } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ts_defns.h000066400000000000000000000122041261471605300166450ustar00rootroot00000000000000/* * Definitions for working with H.222 Transport Stream packets * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _ts_defns #define _ts_defns #include "compat.h" // Transport Stream packets are always the same size #define TS_PACKET_SIZE 188 // When we are putting data into a TS packet, we need the first four // bytes for heading information, which means that we will have at most // 184 bytes for our payload #define MAX_TS_PAYLOAD_SIZE (TS_PACKET_SIZE-4) // ------------------------------------------------------------ // Support for PCR read-ahead buffering // Basically, always ensure that we know have read both the // previous and the next PCR, so we can calculate the actual // PCR for each packet between. // Let's guess for a maximum number of TS entries we're likely to need // to be able to hold... // // XXX But whatever number we guess here will be too small for some // XXX streams, or so big it's really quite over the top for most // XXX (and more than I'd like). So maybe we should have something // XXX that's likely to cope for most streams, and we should (ideally) // XXX have a way for the user to set the size with a swich, but also // XXX (perhaps) we should allow the reader to continue (using the last // XXX calculated rate) if we can't read ahead? Or perhaps having the // XXX switch is enough, for the nonce... Or maybe we should allow the // XXX buffer to grow (on demand, within some sort of reason) if it // XXX needs to. #define PCR_READ_AHEAD_SIZE 20000 // a made-up number struct _ts_pcr_buffer { byte TS_buffer[PCR_READ_AHEAD_SIZE][TS_PACKET_SIZE]; // For convenience (since we'll already have calculated this once), // remember each packets PID uint32_t TS_buffer_pids[PCR_READ_AHEAD_SIZE]; // And the PCR PID we're looking for (we have to assume that's fairly // static, or we couldn't do read-aheads and interpolations) uint32_t TS_buffer_pcr_pid; // The number of TS entries we've got therein, the *last* of which // has a PCR int TS_buffer_len; // Which TS packet we should read next... int TS_buffer_next; // The PCR of that last entry uint64_t TS_buffer_end_pcr; // And the PCR of the *previous* last entry uint64_t TS_buffer_prev_pcr; // From which, we can deduce the time per packet uint64_t TS_buffer_time_per_TS; // For diagnostic purposes, the sequence number of TS_buffer[0] // (and thus, of the overall read-ahead buffer) in the overall file int TS_buffer_posn; // Did we read an EOF before finding a "second" PCR? // (perhaps we should instead call this "TS_playing_out", but that's // less directly named from how we set it) int TS_had_EOF; }; typedef struct _ts_pcr_buffer *TS_pcr_buffer_p; #define SIZEOF_TS_PCR_BUFFER sizeof(struct _ts_pcr_buffer) // ------------------------------------------------------------ // The number of TS packets to read ahead #define TS_READ_AHEAD_COUNT 1024 // aim for multiple of block boundary -- used to be 50 // Thus the number of bytes to read ahead #define TS_READ_AHEAD_BYTES TS_READ_AHEAD_COUNT*TS_PACKET_SIZE // A read-ahead buffer for reading TS packets. // // Note that `posn` always gives the file position of the *next* TS packet to // be read from the file (so after reading a TS packet with // `read_next_TS_packet`, the position of said packet is `posn`-TS_PACKET_SIZE) struct _ts_reader { int file; // the file to read from offset_t posn; // the position of the next-to-be-read TS packet void *handle; // handle to pass to read_fn and seek_fn. // Reader and seek functions. If these are non-NULL we call them // when we would call read() or seek(). int (*read_fn)(void *, byte *, size_t); int (*seek_fn)(void *, offset_t); byte read_ahead[TS_READ_AHEAD_COUNT*TS_PACKET_SIZE]; byte *read_ahead_ptr; // location of next packet in said array byte *read_ahead_end; // pointer just after the end of `read_ahead` // If we are doing PCR read-ahead (so we have exact PCR values for our // TS packets), then we also need: TS_pcr_buffer_p pcrbuf; }; typedef struct _ts_reader *TS_reader_p; #define SIZEOF_TS_READER sizeof(struct _ts_reader) #endif // _ts_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ts_fns.h000066400000000000000000001027221261471605300163410ustar00rootroot00000000000000/* * Functions for working with H.222 Transport Stream packets - in particular, * for writing PES packets. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _ts_fns #define _ts_fns #include "compat.h" #include "h222_defns.h" #include "tswrite_defns.h" #include "pidint_defns.h" #include "ts_defns.h" // ============================================================ // Writing a Transport Stream // ============================================================ /* * Write out a Transport Stream PAT and PMT. * * - `output` is the TS output context returned by `tswrite_open` * - `transport_stream_id` is the id for this particular transport stream. * - `program_number` is the program number to use for the PID. * - `pmt_pid` is the PID for the PMT. * - `pid` is the PID of the stream to enter in the tables. This is also * used as the PCR PID. * - `stream_type` is the type of stream. MPEG-2 video is 0x01, * MPEG-4/AVC (H.264) is 0x1b. * * Since we're outputting a TS representing a single ES, we only need to * support a single program stream, containing a single PID. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_TS_program_data(TS_writer_p output, uint32_t transport_stream_id, uint32_t program_number, uint32_t pmt_pid, uint32_t pid, byte stream_type); /* * Write out a Transport Stream PAT and PMT, for multiple streams. * * - `output` is the TS output context returned by `tswrite_open` * - `transport_stream_id` is the id for this particular transport stream. * - `program_number` is the program number to use for the PMT PID. * - `pmt_pid` is the PID for the PMT. * - `pcr_pid` is the PID that contains the PCR. * - `num_progs` is how many program streams are to be defined. * - `prog_pid` is an array of audio/video PIDs * - `prog_type` is an array of the corresponding stream types * * Note that if `num_progs` is 0, `pcr_pid` is ignored. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_TS_program_data2(TS_writer_p output, uint32_t transport_stream_id, uint32_t program_number, uint32_t pmt_pid, uint32_t pcr_pid, int num_progs, uint32_t prog_pid[], byte prog_type[]); /* * Write out a Transport Stream PAT. * * - `output` is the TS output context returned by `tswrite_open` * - `transport_stream_id` is the id for this particular transport stream. * - `prog_list` is a PIDINT list of program number / PID pairs. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_pat(TS_writer_p output, uint32_t transport_stream_id, pidint_list_p prog_list); /* * Write out a Transport Stream PMT, given a PMT datastructure * * - `output` is the TS output context returned by `tswrite_open` * - `pmt_pid` is the PID for the PMT. * - 'pmt' is the datastructure containing the PMT information * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_pmt(TS_writer_p output, uint32_t pmt_pid, pmt_p pmt); /* * Write out a Transport Stream PAT and PMT, given the appropriate * datastructures * * - `output` is the TS output context returned by `tswrite_open` * - `transport_stream_id` is the id for this particular transport stream. * - `prog_list` is a PIDINT list of program number / PID pairs. * - `pmt_pid` is the PID for the PMT. * - 'pmt' is the datastructure containing the PMT information * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_pat_and_pmt(TS_writer_p output, uint32_t transport_stream_id, pidint_list_p prog_list, uint32_t pmt_pid, pmt_p pmt); /* * Write out a Transport Stream PAT, for a single program. * * - `output` is the TS output context returned by `tswrite_open` * - `transport_stream_id` is the id for this particular transport stream. * - `program_number` is the program number to use for the PID. * - `pmt_pid` is the PID for the PMT. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_single_program_pat(TS_writer_p output, uint32_t transport_stream_id, uint32_t program_number, uint32_t pmt_pid); /* * Write out our ES data as a Transport Stream PES packet. * * - `output` is the TS output context returned by `tswrite_open` * - `data` is our ES data (e.g., a NAL unit) * - `data_len` is its length * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * * If the data to be written is more than 65535 bytes long (i.e., the * length will not fit into 2 bytes), then the PES packet written will * have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0) * 2.4.3.7, Semantic definitions of fields in PES packet). This is only * allowed for video streams. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_ES_as_TS_PES_packet(TS_writer_p output, byte data[], uint32_t data_len, uint32_t pid, byte stream_id); /* * Write out our ES data as a Transport Stream PES packet, with PTS and/or DTS * if we've got them, and some attempt to write out a sensible PCR. * * - `output` is the TS output context returned by `tswrite_open` * - `data` is our ES data (e.g., a NAL unit) * - `data_len` is its length * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * - `got_pts` is TRUE if we have a PTS value, in which case * - `pts` is said PTS value * - `got_dts` is TRUE if we also have DTS, in which case * - `dts` is said DTS value. * * We also want to try to write out a sensible PCR value. * * PTS can go up as well as down (it is the time at which the next frame * should be presented to the user, but frames do not necessarily occur * in presentation order). * * DTS only goes up, since it is the time that the frame should be decoded. * * Thus, if we have it, the DTS is sensible to use for the PCR... * * If the data to be written is more than 65535 bytes long (i.e., the * length will not fit into 2 bytes), then the PES packet written will * have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0) * 2.4.3.7, Semantic definitions of fields in PES packet). This is only * allowed for video streams. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_ES_as_TS_PES_packet_with_pts_dts(TS_writer_p output, byte data[], uint32_t data_len, uint32_t pid, byte stream_id, int got_pts, uint64_t pts, int got_dts, uint64_t dts); /* * Write out our ES data as a Transport Stream PES packet, with PCR. * * - `output` is the TS output context returned by `tswrite_open` * - `data` is our ES data (e.g., a NAL unit) * - `data_len` is its length * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * - `pcr_base` and `pcr_extn` encode the PCR value. * * If the data to be written is more than 65535 bytes long (i.e., the * length will not fit into 2 bytes), then the PES packet written will * have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0) * 2.4.3.7, Semantic definitions of fields in PES packet). This is only * allowed for video streams. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_ES_as_TS_PES_packet_with_pcr(TS_writer_p output, byte data[], uint32_t data_len, uint32_t pid, byte stream_id, uint64_t pcr_base, uint32_t pcr_extn); /* * Write out a PES packet's data as a Transport Stream PES packet. * * - `output` is the TS output context returned by `tswrite_open` * - `data` is our PES data (e.g., a program stream video data packet) * - `data_len` is its length * - `pid` is the PID to use for this TS packet * - `stream_id` is the PES packet stream id to use (e.g., * DEFAULT_VIDEO_STREAM_ID) * - `got_pcr` is TRUE if we have values for the PCR in this packet, * in which case `pcr_base` and `pcr_extn` are the parts of the PCR. * * If the data to be written is more than 65535 bytes long (i.e., the * length will not fit into 2 bytes), then the PES packet written will * have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0) * 2.4.3.7, Semantic definitions of fields in PES packet). This is only * allowed for video streams. * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_PES_as_TS_PES_packet(TS_writer_p output, byte data[], uint32_t data_len, uint32_t pid, byte stream_id, int got_pcr, uint64_t pcr_base, uint32_t pcr_extn); /* * Write out a Transport Stream Null packet. * * - `output` is the TS output context returned by `tswrite_open` * * Returns 0 if it worked, 1 if something went wrong. */ extern int write_TS_null_packet(TS_writer_p output); // ============================================================ // Reading a Transport Stream // ============================================================ // ------------------------------------------------------------ // File handling // ------------------------------------------------------------ /* * Build a TS packet reader, including its read-ahead buffer * * - `file` is the file that the TS packets will be read from * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int build_TS_reader(int file, TS_reader_p *tsreader); /* * Build a TS packet reader using the given functions as read() and seek(). * * Returns 0 on success, 1 on failure. */ extern int build_TS_reader_with_fns(void *handle, int (*read_fn)(void *, byte *, size_t), int (*seek_fn)(void *, offset_t), TS_reader_p *tsreader); /* * Open a file to read TS packets from. * * If `filename` is NULL, then the input will be taken from standard input. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int open_file_for_TS_read(char *filename, TS_reader_p *tsreader); /* * Free a TS packet read-ahead buffer * * Sets `buffer` to NULL. */ extern void free_TS_reader(TS_reader_p *tsreader); /* * Free a TS packet read-ahead buffer and close the referenced file * (if it is not standard input). * * Sets `buffer` to NULL, whether the file close succeeds or not. * * Returns 0 if all goes well, 1 if something goes wrong. */ extern int close_TS_reader(TS_reader_p *tsreader); /* * Seek to a given offset in the TS reader's file * * (This should be used in preference to just seeking on the "bare" file * since it also unsets the read-ahead buffer. However, it is still just * a wrapper around `seek_file`.) * * It is assumed (but not checked) that the seek will end up at an appropriate * offset for reading a TS packet - i.e., presumably some multiple of * TS_PACKET_SIZE. * * Returns 0 if all goes well, 1 if something goes wrong */ extern int seek_using_TS_reader(TS_reader_p tsreader, offset_t posn); /* * Read the (rest of the) first TS packet, given its first four bytes * * This is intended for use after inspecting the first four bytes of the * input file, to determine if the file is TS or PS. * * - `tsreader` is the TS packet reading context * - `start` is the first four bytes of the file * - `packet` is (a pointer to) the resultant TS packet. * * This is a pointer into the reader's read-ahead buffer, and so should not * be freed. Note that this means that it may not persist after another call * of this function (and will not persist after a call of * `free_TS_reader`). * * Note that the caller is trusted to call this only when appropriate. * * Returns 0 if all goes well, EOF if end of file was read, or 1 if some * other error occurred (in which case it will already have output a message * on stderr about the problem). */ extern int read_rest_of_first_TS_packet(TS_reader_p tsreader, byte start[4], byte **packet); /* * Read the next TS packet. * * - `tsreader` is the TS packet reading context * - `packet` is (a pointer to) the resultant TS packet. * * This is a pointer into the reader's read-ahead buffer, and so should not * be freed. Note that this means that it may not persist after another call * of this function (and will not persist after a call of * `free_TS_reader`). * * Returns 0 if all goes well, EOF if end of file was read, or 1 if some * other error occurred (in which case it will already have output a message * on stderr about the problem). */ extern int read_next_TS_packet(TS_reader_p tsreader, byte **packet); // ------------------------------------------------------------ // Reading a transport stream with buffered timing // Keeps a PCR in hand, so that it has accurate timing information // for each TS packet // ------------------------------------------------------------ /* Set up the the "looping" buffered TS packet reader and let it know what its * PCR PID is. * * This must be called before any other _buffered_TS_packet function. * * - `pcr_pid` is the PID within which we should look for PCR entries * * Returns 0 if all went well, 1 if something went wrong (allocating space * for the TS PCR buffer). */ extern int prime_read_buffered_TS_packet(TS_reader_p tsreader, uint32_t pcr_pid); /* Retrieve the first TS packet from the PCR read-ahead buffer, * complete with its calculated PCR time. * * prime_read_buffered_TS_packet() must have been called before this. * * This should be called the first time a TS packet is to be read * using the PCR read-ahead buffer. It "primes" the read-ahead mechanism * by performing the first actual read-ahead. * * - `pcr_pid` is the PID within which we should look for PCR entries * - `start_count` is the index of the current (last read) TS entry (which will * generally be the PMT). * - `data` returns a pointer to the TS packet data * - `pid` is its PID * - `pcr` is its PCR, calculated using the previous known PCR and * the following known PCR. * - `count` is the index of the returned TS packet in the file * * Note that, like read_next_TS_packet, we return a pointer to our data, * and, similarly, warn that it will go away next time this function * is called. * * Returns 0 if all went well, 1 if something went wrong, EOF if EOF was read. */ extern int read_first_TS_packet_from_buffer(TS_reader_p tsreader, uint32_t pcr_pid, uint32_t start_count, byte *data[TS_PACKET_SIZE], uint32_t *pid, uint64_t *pcr, uint32_t *count); /* Retrieve the next TS packet from the PCR read-ahead buffer, * complete with its calculated PCR time. * * - `data` returns a pointer to the TS packet data * - `pid` is its PID * - `pcr` is its PCR, calculated using the previous known PCR and * the following known PCR. * * Note that, like read_next_TS_packet, we return a pointer to our data, * and, similarly, warn that it might go away next time this function * is called. * * Returns 0 if all went well, 1 if something went wrong, EOF if EOF was read. */ extern int read_next_TS_packet_from_buffer(TS_reader_p tsreader, byte *data[TS_PACKET_SIZE], uint32_t *pid, uint64_t *pcr); /* * Read the next TS packet, coping with looping, etc. * * prime_read_buffered_TS_packet() should have been called first. * * This is a convenience wrapper around read_first_TS_packet_from_buffer() * and read_next_TS_packet_from_buffer(). * * This differs from ``read_TS_packet`` in that it assumes that the * underlying code will already have read to the next PCR, so that * it can know the *actual* (PCR-based) time for each TS packet. * * - `tsreader` is the TS reader context * - `count` is a running count of TS packets read from this input * - `data` is a pointer to the data for the packet * - `pid` is the PID of the TS packet * - `pcr` is the PCR value (possibly calculated) for this TS packet * - if `max` is greater than zero, then at most `max` TS packets should * be read from the input * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) - i.e., rewind to `start_posn` and start again if * `count` reaches `max` (obviously only if `max` is greater than zero). * - `start_count` is the value `count` should have after we've looped back * to `start_posn` * - if `quiet` is true, then only error messages should be written out * * Returns 0 if all went well, 1 if something went wrong, EOF if `loop` is * false and either EOF was read, or `max` TS packets were read. */ extern int read_buffered_TS_packet(TS_reader_p tsreader, uint32_t *count, byte *data[TS_PACKET_SIZE], uint32_t *pid, uint64_t *pcr, int max, int loop, offset_t start_posn, uint32_t start_count, int quiet); // ------------------------------------------------------------ // Packet interpretation // ------------------------------------------------------------ /* * Retrieve the PCR (if any) from a TS packet's adaptation field * * - `adapt` is the adaptation field content * - `adapt_len` is its length * - `got_PCR` is TRUE if the adaptation field contains a PCR * - `pcr` is then the PCR value itself */ extern void get_PCR_from_adaptation_field(byte adapt[], int adapt_len, int *got_pcr, uint64_t *pcr); /* * Report on the contents of this TS packet's adaptation field * * - `adapt` is the adaptation field content * - `adapt_len` is its length * * Returns 0 if all went well, 1 if something went wrong. */ extern void report_adaptation_field(byte adapt[], int adapt_len); /* * Report on the timing information from this TS packet's adaptation field * * - if `times` is non-NULL, then timing information (derived from the PCR) * will be calculated and reported * - `adapt` is the adaptation field content * - `adapt_len` is its length * - `packet_count` is a count of how many TS packets up to now * * Returns 0 if all went well, 1 if something went wrong. */ extern void report_adaptation_timing(timing_p times, byte adapt[], int adapt_len, int packet_count); /* * Report on the contents of this TS packet's payload. The packet is assumed * to have a payload that is (part of) a PES packet. * * - if `show_data` then the data for the PES packet will also be shown * - `stream_type` is the stream type of the data, or -1 if it is not * known * - `payload` is the payload of the TS packet. We know it can't be more * than 184 bytes long, because of the packet header bytes. * - regardless, `payload_len` is the actual length of the payload. * * Returns 0 if all went well, 1 if something went wrong. */ extern void report_payload(int show_data, int stream_type, byte payload[MAX_TS_PAYLOAD_SIZE], int payload_len, int payload_unit_start_indicator); /* * Print out information about program descriptors * (either from the PMT program info, or the PMT/stream ES info) * * - if `is_msg` then print as a message, otherwise as an error * - `leader1` and `leader2` are the text to write at the start of each line * (either or both may be NULL) * - `desc_data` is the data containing the descriptors * - `desc_data_len` is its length * * Returns 0 if all went well, 1 if something went wrong */ extern int print_descriptors(int is_msg, char *leader1, char *leader2, byte *desc_data, int desc_data_len); /* * Extract the program list from a PAT packet (PID 0x0000). * * Handles the result of calling build_psi_data() for this PAT. * * - if `verbose`, then report on what we're doing * - `payload` is the payload of the TS packet. We know it can't be more * than 184 bytes long, because of the packet header bytes. * - regardless, `payload_len` is the actual length of the payload. * - `prog_list` is the list of program numbers versus PIDs. * * Returns 0 if all went well, 1 if something went wrong. */ extern int extract_prog_list_from_pat(int verbose, byte payload[MAX_TS_PAYLOAD_SIZE], int payload_len, pidint_list_p *prog_list); /* * Extract the stream list (and PCR PID) from a PMT packet. * * Handles the result of calling build_psi_data() for this PMT. * * - if `verbose`, then report on what we're doing * - `payload` is the payload of the TS packet. We know it can't be more * than 184 bytes long, because of the packet header bytes. * - regardless, `payload_len` is the actual length of the payload. * - `pid` is the PID of this TS packet. * - `program_number` is the program number. * - `pcr_pid` is the PID of packets containing the PCR, or 0. * - `stream_list` is a list of stream versus PID. * * Returns 0 if all went well, 1 if something went wrong. */ extern int extract_stream_list_from_pmt(int verbose, byte payload[MAX_TS_PAYLOAD_SIZE], int payload_len, uint32_t pid, int *program_number, uint32_t *pcr_pid, pidint_list_p *stream_list); /* * Given a TS packet, extract the (next bit of) a PAT/PMT's data. * * - if `verbose`, then report on what we're doing * - `payload` is the payload of the current TS packet. We know it can't be * more than 184 bytes long, because of the packet header bytes. * - regardless, `payload_len` is the actual length of the payload. * - `pid` is the PID of this TS packet. * - `data` is the data array for the whole of the data of this PSI. * If it is passed as NULL, then the TS packet must be the first for * this PSI, and this function will malloc an array of the appropriate * length (and return it here). If it is non-NULL, then it is partially * full. * - `data_len` is the actual length of the `data` array -- if `data` is NULL * then this will be set by the function. * - `data_used` is how many bytes of data are already in the `data` array. * This will be updated by this function - if it is returned as equal to * `data_len`, then the PMT packet data is complete. * * Usage: * * If a PSI packet has PUSI set, then it is the first packet of said PSI * (which, for our purposes, means PAT or PMT). If it does not, then it * is a continuation. If PUSI was set, call this with ``data`` NULL, otherwise * pass it some previous data to continue. * * Returns 0 if all went well, 1 if something went wrong. */ extern int build_psi_data(int verbose, byte payload[MAX_TS_PAYLOAD_SIZE], int payload_len, uint32_t pid, byte **data, int *data_len, int *data_used); /* * Extract the program map table from a PMT packet. * * Assumes that the whole content of the PMT is in this single packet. * * - `data` is the data for the PMT packet. * - `data_len` is the length of said data. * - `pid` is the PID of this PMT * - `pmt` is the new PMT datastructure * * Returns 0 if all went well, 1 if something went wrong. */ extern int extract_pmt(int verbose, byte data[], int data_len, uint32_t pid, pmt_p *pmt); /* * Split a TS packet into its main parts * * - `buf` is the data for the packet * - `pid` is the PID of said data * - `payload_unit_start_indicator` is TRUE if any payload in this * packet forms the start of a PES packet. Its meaning is not significant * if there is no payload, or if the payload is not (part of) a PES packet. * - `adapt` is an offset into `buf`, acting as an array of the actual * adaptation control bytes. It will be NULL if there are no adaptation * controls. * - `adapt_len` is the length of the adaptation controls (i.e., the * number of bytes). It will be 0 if there are no adaptation controls. * - `payload` is an offset into `buf`, acting as an array of the actual * payload bytes. It will be NULL if there is no payload. * - `payload_len` is the length of the payload *in this packet* (i.e., the * number of bytes. It will be 0 if there is no payload. * * Returns 0 if all went well, 1 if something went wrong. */ extern int split_TS_packet(byte buf[TS_PACKET_SIZE], uint32_t *pid, int *payload_unit_start_indicator, byte *adapt[], int *adapt_len, byte *payload[], int *payload_len); /* * Return the next TS packet, as payload and adaptation controls. * * This is a convenience wrapping of `read_next_TS_packet` and * `split_TS_packet`. Because of this, the data referenced by `adapt` and * `payload` will generally not persist over further calls of this function * and `read_next_TS_packet`, as it is held within the TS reader's read-ahead * buffer. * * - `tsreader` is the TS packet reading context * - `pid` is the PID of said data * - `payload_unit_start_indicator` is TRUE if any payload in this * packet forms the start of a PES packet. Its meaning is not significant * if there is no payload, or if the payload is not (part of) a PES packet. * - `adapt` is an offset into `buf`, acting as an array of the actual * adaptation control bytes. It will be NULL if there are no adaptation * controls. * - `adapt_len` is the length of the adaptation controls (i.e., the * number of bytes). It will be 0 if there are no adaptation controls. * - `payload` is an offset into `buf`, acting as an array of the actual * payload bytes. It will be NULL if there is no payload. * - `payload_len` is the length of the payload *in this packet* (i.e., the * number of bytes. It will be 0 if there is no payload. * * Returns 0 if all went well, EOF if there is no more data, 1 if something * went wrong. */ extern int get_next_TS_packet(TS_reader_p tsreader, uint32_t *pid, int *payload_unit_start_indicator, byte *adapt[], int *adapt_len, byte *payload[], int *payload_len); /* * Find the first (next) PAT. * * - `tsreader` is the TS packet reading context * - if `max` is non-zero, then it is the maximum number of TS packets to read * - if `verbose` is true, then output extra information * - if `quiet` is true, then don't output normal informational messages * - `num_read` is the number of packets read to find the PAT (or before * giving up) * - `prog_list` is the program list from the PAT, or NULL if none was found * * Returns 0 if all went well, EOF if no PAT was found, * 1 if something else went wrong. */ extern int find_pat(TS_reader_p tsreader, int max, int verbose, int quiet, int *num_read, pidint_list_p *prog_list); /* * Find the next PMT, and report on it. * * - `tsreader` is the TS packet reading context * - `pmt_pid` is the PID of the PMT we are looking for * - if `program_number` is -1, then any PMT with that PID is acceptable, * otherwise we're only interested in a PMT with that PID and the given * program number. * - if `max` is non-zero, then it is the maximum number of TS packets to read * - if `verbose` is true, then output extra information * - if `quiet` is true, then don't output normal informational messages * - `num_read` is the number of packets read to find the PMT (or before * giving up) * - `pmt` is a new datastructure representing the PMT found * * Returns 0 if all went well, EOF if no PMT was found, * 1 if something else went wrong. */ extern int find_next_pmt(TS_reader_p tsreader, uint32_t pmt_pid, int program_number, int max, int verbose,int quiet, int *num_read, pmt_p *pmt); /* * Find the next PAT, and from that the next PMT. * * Looks for the next PAT in the input stream, and then for the first * PMT thereafter. If there is more than one program stream in the PAT, * it looks for the PMT for the first. * * - `tsreader` is the TS packet reading context * - if `max` is non-zero, then it is the maximum number of TS packets to read * - if `verbose` is true, then output extra information * - if `quiet` is true, then don't output normal informational messages * - `num_read` is the number of packets read to find the PMT (or before * giving up) * - `pmt` is a new datastructure containing the information from the PMT. * * Returns 0 if all went well, EOF if no PAT or PMT was found (and thus * no program stream), -2 if a PAT was found but it did not contain any * programs, 1 if something else went wrong. */ extern int find_pmt(TS_reader_p tsreader, const int req_prog_no, int max, int verbose, int quiet, int *num_read, pmt_p *pmt); #endif // _ts_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/ts_packet_insert.c000066400000000000000000000241521261471605300204010ustar00rootroot00000000000000/* * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Gareth Bailey (gb@kynesim.co.uk), Kynesim, Cambridge, UK * * ***** END LICENSE BLOCK ***** */ #include "compat.h" #include #include #include #include #include #include #include #include "misc_fns.h" #include "printing_fns.h" #include "version.h" #define TS_PACKET_SIZE 188 #define TO_BE16(from,to) *to = (0xFF & (from>>8)); *(to+1) = (0xFF & from); static uint8_t *create_out_packet(char *in_data, int in_len, uint16_t pid) { uint8_t *out_packet = malloc(TS_PACKET_SIZE); uint8_t *ptr = out_packet; uint16_t flags; uint16_t flags_pid; if (!ptr) return NULL; if (in_len > (TS_PACKET_SIZE - 4)) return NULL; *ptr = 0x47; ptr++; /* Transport Error Indicator */ flags = 0<<15; /* Payload Unit Start Indicator */ flags = flags | 0<<14; /* Transport Priority */ flags = flags | 0<<13; flags_pid = flags | pid; TO_BE16(flags_pid,ptr); ptr+=2; *ptr = 0x11; ptr++; memcpy(ptr,in_data,in_len); ptr+=in_len; memset(ptr,0xFF,TS_PACKET_SIZE-(ptr-out_packet)); ptr+=TS_PACKET_SIZE-(ptr-out_packet); assert((ptr-TS_PACKET_SIZE) == out_packet); { int i; ptr = out_packet; print_msg("Packet to be written is:\n"); for (i=0;i\n" "\n" ); REPORT_VERSION("ts_packet_insert"); print_msg( "\n" " Insert TS packets into a Transport Stream at positions\n" " specified by the user.\n" "\n" "Input:\n" " An H.222 Transport Stream file.\n" "\n" "Switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -p This a a colon (':') delimited string of numbers\n" " between 0 and 1, representing how far through to put \n" " each TS packet. E.g., -p 0.1:0.4:0.7:0.9 will insert\n" " 4 packets at 10%%, 40%%, 70%% and 90%% through the file.\n" " -pid The inserted packets will have the PID specfied.\n" " If no PID is specified, then 0x68 will be used.\n" " -s The inserted packets will contain as their\n" " payload. This defaults to 'Inserted packet'.\n" " -o The new TS file will be written out with the given name\n" " (which defaults to out.ts)\n" "For example:\n" "\n" " ts_packet_insert -p 0.3:0.6 -o out.ts -pid 89 -s \"AD=start\" in.ts\n" ); } /* bubble sort */ static void sort_positions(double *in_array,int size) { int sorted=0; while (!sorted) { int i; /* asume sorted */ sorted++; for (i=0;iin_array[i+1]) { double tmp; tmp = in_array[i]; in_array[i] = in_array[i+1]; in_array[i+1] = tmp; /* damn, go round again */ sorted=0; } } } } int main(int argc, char **argv) { char *output_file_path = "out.ts"; char *in_file_path = NULL; long in_file_size=0; /*an array of floats for the positions of packets to insert,values of 0-1*/ double *positions=NULL; int *packet_numbers=NULL; int n_pos = 0; int argno = 1; int arg_counter = 0; uint32_t pid=0x68; char *out_string="Inserted packet"; if (argc < 2) { print_usage(); return 0; } while (argno < argc) { if (argv[argno][0] == '-') { if (!strcmp("-p",argv[argno])) { char *endptr; char *position_string; int pos_index; ++argno; free(positions); n_pos = (num_char_in_string(argv[argno],':')+1); positions = malloc(n_pos * sizeof(double)); if (!positions) { print_err("malloc failed"); exit(1); } position_string = strtok(argv[argno],":"); pos_index=0; print_msg("Adding new packets at:"); while (1) { if (!position_string) break; positions[pos_index] = strtod(position_string,&endptr); if (endptr == position_string || positions[pos_index]>1 || positions[pos_index]<0) { fprint_err("\nNot a valid floating point number for position (argument %d)\n",argno); exit(1); } fprint_msg(" %d%%",(int)(positions[pos_index]*100)); position_string = strtok(NULL,":"); pos_index++; } print_msg("\n"); sort_positions(positions,n_pos); assert(pos_index == n_pos); } else if (!strcmp("-err",argv[argno])) { CHECKARG("ts_packet_insert",argno); if (!strcmp(argv[argno+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[argno+1],"stdout")) redirect_output_stdout(); else { fprint_err("### ts_packet_insert: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[argno+1]); return 1; } argno++; } else if (!strcmp("-pid",argv[argno])) { int err; CHECKARG("ts_packet_insert",argno); err = unsigned_value("ts_packet_insert",argv[argno],argv[argno+1],0,&pid); if (err) return 1; argno++; } else if (!strcmp("-o",argv[argno])) { CHECKARG("ts_packet_insert",argno); output_file_path = argv[++argno]; } else if (!strcmp("-s",argv[argno])) { CHECKARG("ts_packet_insert",argno); out_string = argv[++argno]; } else if (!strcmp("-h",argv[argno]) || !strcmp("--help",argv[argno])) { print_usage(); return 0; } else { fprint_msg("\n *** Unknown option %s, ignoring.\n\n",argv[argno]); } } else { if (arg_counter == 0) { in_file_path = argv[argno]; arg_counter++; } else { fprint_err( "### ts_packet_insert: Unexpected '%s'\n", argv[argno]); return 1; } } argno++; } if (!in_file_path) { print_err("Error: No input file specified.\n"); exit(1); } fprint_msg("Reading from file: %s\n",in_file_path); fprint_msg("Writing to file: %s\n",output_file_path); fprint_msg("Inserting packets with PID %#x (%u)\n",pid,pid); fprint_msg("Using output string: %s\n",out_string); { int out_file; int in_file = open(in_file_path,O_RDONLY); if (in_file<0) { fprint_err("Error: could not open %s for reading: %s\n", in_file_path,strerror(errno)); exit(1); } out_file = open(output_file_path,O_WRONLY | O_TRUNC | O_CREAT,0644); if (out_file<0) { fprint_err("Error: could not open %s for reading: %s\n", output_file_path,strerror(errno)); exit(1); } in_file_size = get_file_size(in_file); if (in_file_size % TS_PACKET_SIZE) { print_err("Error: TS file length is not a multiple of 188 bytes\n"); exit(1); } { int num_pack = in_file_size / TS_PACKET_SIZE; int i; fprint_msg("\nInput file is %ld bytes long with ",in_file_size); fprint_msg("%d TS packets\n",num_pack); packet_numbers = malloc(n_pos * sizeof(int)); /* Find out which packets we insert before */ for (i=0;i #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ts_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "pidint_fns.h" #include "es_fns.h" #include "pes_fns.h" #include "version.h" #include "fmtx.h" // A three-way choice for what to output by PID enum pid_extract { EXTRACT_UNDEFINED, EXTRACT_TS, // Output the first "named" video stream EXTRACT_PID, // Output an explicit PID }; typedef enum pid_extract EXTRACT; typedef struct dvbdata_s { int found; int pts_valid; int dts_valid; unsigned int data_len; uint64_t pts; uint64_t last_pts; uint64_t dts; uint8_t data[0x10000]; } dvbdata_t; static dvbdata_t dvbd = {0}; static int tfmt = FMTX_TS_DISPLAY_90kHz_RAW; #define PROGNAME "tsdvbsub" static inline unsigned int mem16be(const uint8_t * p) { return (p[0] << 8) | p[1]; } static const uint8_t * page_composition_segment(dvbdata_t * const dvbd, const uint8_t * p, int segment_length) { const uint8_t * const eos = p + segment_length; const char * state_text[] = { "normal", "acquisition point", "mode change", "reserved" }; int page_state; fprint_msg("\npage_composition_segment\n"); fprint_msg("page_time_out: %d\n", *p++); fprint_msg("page_version_number: %d\n", p[0] >> 4); page_state = (p[0] >> 2) & 3; fprint_msg("page_state: %d (%s)\n", page_state, state_text[page_state]); fprint_msg("reserved: %#x\n", p[0] & 3); ++p; while (p < eos) { fprint_msg("region_id: %d\n", *p++); fprint_msg("reserved: %#x\n", *p++); fprint_msg("region_horizontal_address: %d\n", mem16be(p)); p += 2; fprint_msg("region_vertical_address: %d\n", mem16be(p)); p += 2; } return p; } static const uint8_t * region_composition_segment(dvbdata_t * const dvbd, const uint8_t * p, int segment_length) { const uint8_t * const eos = p + segment_length; int region_fill_flag; fprint_msg("\nregion_composition_segment\n"); fprint_msg("region_id: %d\n", *p++); fprint_msg("region_version_number: %d\n", p[0] >> 4); region_fill_flag = (p[0] >> 3) & 1; fprint_msg("region_fill_flag: %d\n", region_fill_flag); fprint_msg("reserved: %#x\n", p[0] & 7); ++p; fprint_msg("region_width: %d\n", mem16be(p)); p += 2; fprint_msg("region_height: %d\n", mem16be(p)); p += 2; fprint_msg("region_level_of_complexity: %d\n", p[0] >> 5); fprint_msg("region_depth: %d\n", (p[0] >> 2) & 7); fprint_msg("reserved: %#x\n", p[0] & 3); ++p; fprint_msg("CLUT_id: %d\n", *p++); fprint_msg("region_8-bit_pixel_code: %d\n", *p++); fprint_msg("region_4-bit_pixel_code: %d\n", p[0] >> 4); fprint_msg("region_2-bit_pixel_code: %d\n", (p[0] >> 2) & 3); fprint_msg("reserved: %#x\n", p[0] & 3); ++p; while (p < eos) { int object_type; fprint_msg("object_id: %d\n", mem16be(p)); p += 2; fprint_msg("object_type: %d\n", object_type = (p[0] >> 6)); fprint_msg("object_provider_flag: %d\n", (p[0] >> 4) & 3); fprint_msg("object_horizontal_position: %d\n", mem16be(p) & 0xfff); p += 2; fprint_msg("reserved: %#x\n", p[0] >> 4); fprint_msg("object_vertical_position: %d\n", mem16be(p) & 0xfff); p += 2; if (object_type == 1 || object_type == 2) { fprint_msg("foreground_pixel_code: %d\n", *p++); fprint_msg("background_pixel_code: %d\n", *p++); } } return p; } static const uint8_t * CLUT_definition_segment(dvbdata_t * const dvbd, const uint8_t * p, int segment_length) { const uint8_t * const eos = p + segment_length; fprint_msg("\nCLUT definition_segment\n"); fprint_msg("CLUT_id: %d\n", *p++); fprint_msg("CLUT_version_number: %d\n", p[0] >> 4); fprint_msg("reserved: %#x\n", p[0] & 0xf); ++p; while (p < eos) { int full_range_flag; fprint_msg("CLUT_entry_id: %d\n", *p++); fprint_msg("2-bit/entry_CLUT_flag: %d\n", p[0] >> 7); fprint_msg("4-bit/entry_CLUT_flag: %d\n", (p[0] >> 6) & 1); fprint_msg("8-bit/entry_CLUT_flag: %d\n", (p[0] >> 5) & 1); fprint_msg("reserved: %#x\n", (p[0] >> 1) & 0xf); fprint_msg("full_range_flag: %#x\n", full_range_flag = (p[0] & 1)); ++p; if (full_range_flag == 1) { fprint_msg("Y-value: %d\n", *p++); fprint_msg("Cr-value: %d\n", *p++); fprint_msg("Cb-value: %d\n", *p++); fprint_msg("T-value: %d\n", *p++); } else { fprint_msg("Y-value: %d\n", p[0] >> 2); fprint_msg("Cr-value: %d\n", ((p[0] & 3) << 2) | ((p[1] >> 6) & 3)); fprint_msg("Cb-value: %d\n", (p[1] >> 2) & 0xf); fprint_msg("T-value: %d\n", p[1] & 3); p += 2; } } return p; } static const uint8_t * object_data_segment(dvbdata_t * const dvbd, const uint8_t * p, int segment_length) { const uint8_t * const sos = p; const uint8_t * const eos = p + segment_length; int object_coding_method; fprint_msg("\nobject_data_segment\n"); fprint_msg("object_id: %d\n", mem16be(p)); p += 2; fprint_msg("object_version_number: %d\n", p[0] >> 4); fprint_msg("object_coding_method: %d\n", object_coding_method = ((p[0] >> 2) & 3)); fprint_msg("non_modifying_colour_flag: %d\n", (p[0] >> 1) & 1); fprint_msg("reserved: %#x\n", p[0] & 0x1); ++p; switch (object_coding_method) { case 0: { unsigned int top_field_data_block_length; unsigned int bottom_field_data_block_length; fprint_msg("top_field_data_block_length: %d\n", top_field_data_block_length = mem16be(p)); p += 2; fprint_msg("bottom_field_data_block_length: %d\n", bottom_field_data_block_length = mem16be(p)); p += 2; print_data(TRUE, "top pixel-data:", p, top_field_data_block_length, 0x10000); p += top_field_data_block_length; print_data(TRUE, "bottom pixel-data:", p, bottom_field_data_block_length, 0x10000); p += bottom_field_data_block_length; if (((p - sos) & 1) != 0) { fprint_msg("8_stuff_bits: %d\n", *p++); } break; } case 1: { unsigned int number_of_codes; unsigned int i; fprint_msg("number_of_codes: %d\n", number_of_codes = *p++); p += 2; for (i = 0; i != number_of_codes; ++i) { fprint_msg("character_code: %d\n", mem16be(p)); p += 2; } break; } default: print_data(TRUE, "reserved:", p, eos - p, 0x10000); break; } return p; } static const uint8_t * subtitling_segment(dvbdata_t * const dvbd, const uint8_t * p) { unsigned int segment_type; unsigned int segment_length; const uint8_t * p2; fprint_msg("\nsubtitling_segment\n"); fprint_msg("sync_byte: %#x\n", *p++); fprint_msg("segment_type: %#x\n", segment_type = *p++); fprint_msg("page_id: %d\n", (p[0] << 8) | p[1]); p += 2; fprint_msg("segment_length: %d\n", segment_length = ((p[0] << 8) | p[1])); p += 2; switch (segment_type) { case 0x10: p2 = page_composition_segment(dvbd, p, segment_length); break; case 0x11: p2 = region_composition_segment(dvbd, p, segment_length); break; case 0x12: p2 = CLUT_definition_segment(dvbd, p, segment_length); break; case 0x13: p2 = object_data_segment(dvbd, p, segment_length); break; default: print_data(TRUE, "data", p, segment_length, segment_length); p2 = p + segment_length; break; } // Always believe seg length in case of early return p += segment_length; if (p != p2) { fprint_msg("### parse length mismatch\n"); } return p; } static void flush_dvbd(dvbdata_t * const dvbd) { const uint8_t * p = dvbd->data; if (!dvbd->found) return; fprint_msg("\nPTS: %s, DTS: %s, PTS - last_PTS: %s\n", !dvbd->pts_valid ? "none" : fmtx_timestamp(dvbd->pts, tfmt), !dvbd->dts_valid ? "none" : fmtx_timestamp(dvbd->dts, tfmt), !dvbd->pts_valid ? "????" : fmtx_timestamp(dvbd->pts - dvbd->last_pts, tfmt)); if (dvbd->pts_valid) dvbd->last_pts = dvbd->pts; fprint_msg("data length: %d\n\n", dvbd->data_len); fprint_msg("data_identifier: %#x\n", *p++); fprint_msg("subtitle_stream_id: %d\n", *p++); while (*p == 0xf) { p = subtitling_segment(dvbd, p); } fprint_msg("end_of_PES_data_field_marker: %#x\n", *p++); if (dvbd->data_len > (unsigned int)(p - dvbd->data)) { print_data(TRUE, "excess bytes", p, dvbd->data_len - (p - dvbd->data), 0x10000); } else if (dvbd->data_len < (unsigned int)(p - dvbd->data)) { fprint_msg("### overrun\n"); } dvbd->data_len = 0; dvbd->pts_valid = FALSE; dvbd->dts_valid = FALSE; dvbd->found = FALSE; memset(dvbd->data, 0, sizeof(dvbd->data)); } static void add_data_dvbd(dvbdata_t * const dvbd, const uint8_t * const data, unsigned int len) { unsigned int gap = sizeof(dvbd->data) - dvbd->data_len; if (len == 0) return; if (gap < len) { fprint_err("### Data buffer overflow\n"); len = gap; } memcpy(dvbd->data + dvbd->data_len, data, len); dvbd->data_len += len; } /* * Extract all the TS packets for a nominated PID to another file. * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_pid_packets(TS_reader_p tsreader, uint32_t pid_wanted, int max, int verbose, int quiet) { int err; int count = 0; int extracted = 0; int pes_packet_len = 0; int got_pes_packet_len = FALSE; // It doesn't make sense to start outputting data for our PID until we // get the start of a packet int need_packet_start = TRUE; for (;;) { uint32_t pid; int payload_unit_start_indicator; byte *adapt, *payload; int adapt_len, payload_len; if (max > 0 && count >= max) { if (!quiet) fprint_msg("Stopping after %d packets\n",max); break; } err = get_next_TS_packet(tsreader,&pid, &payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err == EOF) break; else if (err) { print_err("### Error reading TS packet\n"); return 1; } count++; // If the packet is empty, all we can do is ignore it if (payload_len == 0) continue; if (pid == pid_wanted) { byte *data; int data_len; int pes_overflow = 0; if (verbose) { fprint_msg("%4d: TS Packet PID %04x",count,pid); if (payload_unit_start_indicator) print_msg(" (start)"); else if (need_packet_start) print_msg(" "); print_msg("\n"); } if (payload_unit_start_indicator) { // It's the start of a PES packet, so we need to drop the header int offset; if (need_packet_start) need_packet_start = FALSE; pes_packet_len = (payload[4] << 8) | payload[5]; if (verbose) fprint_msg("PES packet length %d\n",pes_packet_len); got_pes_packet_len = (pes_packet_len > 0); flush_dvbd(&dvbd); err = find_PTS_DTS_in_PES(payload,payload_len, &dvbd.pts_valid, &dvbd.pts, &dvbd.dts_valid, &dvbd.dts); dvbd.found = TRUE; if (IS_H222_PES(payload)) { // It's H.222.0 - payload[8] is the PES_header_data_length, // so our ES data starts that many bytes after that field offset = payload[8] + 9; } else { // We assume it's MPEG-1 offset = calc_mpeg1_pes_offset(payload,payload_len); } data = &payload[offset]; data_len = payload_len-offset; if (verbose) print_data(TRUE,"data",data,data_len,1000); } else { // If we haven't *started* a packet, we can't use this, // since it will just look like random bytes when written out. if (need_packet_start) { continue; } data = payload; data_len = payload_len; if (verbose) print_data(TRUE,"Data",payload,payload_len,1000); } if (got_pes_packet_len) { // Try not to write more data than the PES packet declares if (data_len > pes_packet_len) { pes_overflow = data_len - pes_packet_len; data_len = pes_packet_len; pes_packet_len = 0; } else pes_packet_len -= data_len; } add_data_dvbd(&dvbd, data, data_len); if (got_pes_packet_len && pes_packet_len == 0) { flush_dvbd(&dvbd); } if (pes_overflow) { print_data(TRUE, "Data after PES", data + data_len, pes_overflow, 1000); } extracted ++; } } if (!quiet) fprint_msg("Extracted %d of %d TS packet%s\n", extracted,count,(count==1?"":"s")); // If the user has forgotten to say -pid XX, or -video/-audio, // and are piping the output to another program, it can be surprising // if there is no data! if (quiet && extracted == 0) fprint_err("### No data extracted for PID %#04x (%d)\n", pid_wanted,pid_wanted); return 0; } /* * Extract all the TS packets for either a video or audio stream. * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_av(int input, const int prog_no, int max, int verbose, int quiet) { int err, ii; int max_to_read = max; int total_num_read = 0; uint32_t pid = 0; TS_reader_p tsreader = NULL; pmt_p pmt = NULL; // Turn our file into a TS reader err = build_TS_reader(input,&tsreader); if (err) return 1; // First, find out what program streams we actually have for (;;) { int num_read; // Give up if we've read more than our limit if (max > 0 && max_to_read <= 0) break; err = find_pmt(tsreader,prog_no,max_to_read,verbose,quiet,&num_read,&pmt); if (err == EOF) { if (!quiet) print_msg("No program stream information in the input file\n"); free_TS_reader(&tsreader); free_pmt(&pmt); return 0; } else if (err) { print_err("### Error finding program stream information\n"); free_TS_reader(&tsreader); free_pmt(&pmt); return 1; } max_to_read -= num_read; total_num_read += num_read; // From that, find a stream of the type we want... // Note that the audio detection will accept either DVB or ADTS Dolby (AC-3) // stream types for (ii=0; ii < pmt->num_streams; ii++) { if (pmt->streams[ii].stream_type == 6 && pmt->streams[ii].ES_info_length > 0 && pmt->streams[ii].ES_info[0] == 0x59) { pid = pmt->streams[ii].elementary_PID; break; } } free_pmt(&pmt); // Did we find what we want? If not, go round again and look for the // next PMT (subject to the number of records we're willing to search) if (pid != 0) break; } if (pid == 0) { fprint_err("### No DVB subtitle stream specified in first %d TS packets in input file\n", max); free_TS_reader(&tsreader); return 1; } if (!quiet) fprint_msg("Extracting DVB Subtitles PID %04x (%d)\n", pid,pid); // Amend max to take account of the packets we've already read max -= total_num_read; // And do the extraction. err = extract_pid_packets(tsreader,pid,max,verbose,quiet); free_TS_reader(&tsreader); return err; } /* * Extract all the TS packets for a nominated PID to another file. * * Returns 0 if all went well, 1 if something went wrong. */ static int extract_pid(int input, uint32_t pid_wanted, int max, int verbose, int quiet) { int err; TS_reader_p tsreader = NULL; // Turn our file into a TS reader err = build_TS_reader(input,&tsreader); if (err) return 1; err = extract_pid_packets(tsreader,pid_wanted,max,verbose,quiet); free_TS_reader(&tsreader); return err; } static void print_usage() { print_msg( "Usage: " PROGNAME " [switches] \n" "\n" ); REPORT_VERSION(PROGNAME); print_msg( "\n" " Parse & dump the contents of a single DVB subtitling stream from a\n" " Transport Stream\n" " (or Program Stream).\n" "\n" "Files:\n" " is an H.222 Transport Stream file (but see -stdin and -pes)\n" "\n" "Which stream to extract:\n" " -pid Output data for the stream with the given\n" " . Use -pid 0x to specify a hex value\n" " [default] The stream will be located from the PMT info\n" " -prog Program number [default=1]\n" "\n" "General switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Input from standard input, instead of a file\n" " -verbose, -v Output informational/diagnostic messages\n" " -quiet, -q Only output error messages\n" " -max , -m Maximum number of TS packets to read\n" ); } int main(int argc, char **argv) { int use_stdin = FALSE; char *input_name = NULL; int had_input_name = FALSE; EXTRACT extract = EXTRACT_TS; int input = -1; // Our input file descriptor int maxts = 0; // The maximum number of TS packets to read (or 0) uint32_t pid = 0; // The PID of the (single) stream to extract int quiet = FALSE; // True => be as quiet as possible int verbose = FALSE; // True => output diagnostic/progress messages int prog_no = 1; int err = 0; int ii = 1; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG(PROGNAME, ii); err = int_value(PROGNAME, argv[ii],argv[ii+1],TRUE,10,&maxts); if (err) return 1; ii++; } else if (!strcmp("-pid",argv[ii])) { CHECKARG(PROGNAME,ii); err = unsigned_value(PROGNAME,argv[ii],argv[ii+1],0,&pid); if (err) return 1; ii++; extract = EXTRACT_PID; } else if (!strcmp("-prog",argv[ii])) { CHECKARG(PROGNAME,ii); err = int_value(PROGNAME, argv[ii],argv[ii+1],TRUE,10,&prog_no); if (err) return 1; ii++; } else if (!strcmp("-stdin",argv[ii])) { use_stdin = TRUE; had_input_name = TRUE; // so to speak } else if (!strcmp("-err",argv[ii])) { CHECKARG(PROGNAME,ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### " PROGNAME ": " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-tfmt",argv[ii])) { CHECKARG(PROGNAME,ii); if ((tfmt = fmtx_str_to_timestamp_flags(argv[ii + 1])) < 0) { fprint_msg("### tsreport: Bad timestamp format '%s'\n",argv[ii+1]); return 1; } ii++; } else { fprint_err("### " PROGNAME ": " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name) { fprint_err("### " PROGNAME ": Unexpected '%s'\n",argv[ii]); return 1; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### " PROGNAME ": No input file specified\n"); return 1; } // ============================================================ if (use_stdin) input = STDIN_FILENO; else { input = open_binary_file(input_name,FALSE); if (input == -1) { fprint_err("### " PROGNAME ": Unable to open input file %s\n",input_name); return 1; } } if (!quiet) fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); if (!quiet) { if (extract == EXTRACT_PID) fprint_msg("Extracting packets for PID %04x (%d)\n",pid,pid); } if (maxts != 0 && !quiet) fprint_msg("Stopping after %d TS packets\n",maxts); if (extract == EXTRACT_PID) err = extract_pid(input,pid,maxts,verbose,quiet); else err = extract_av(input,prog_no,maxts,verbose,quiet); if (err) { print_err("### " PROGNAME ": Error extracting data\n"); if (!use_stdin) (void) close_file(input); return 1; } if (!use_stdin) { err = close_file(input); if (err) fprint_err("### " PROGNAME ": Error closing input file %s\n",input_name); } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tsfilter.c000066400000000000000000000177021261471605300166770ustar00rootroot00000000000000/* * Filter a transport stream by a list of pids. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include #include #include #include "compat.h" #include "ts_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "pidint_fns.h" #include "version.h" #include "tswrite_defns.h" #include "tswrite_fns.h" /** List of PIDs to filter in */ int *pidList = NULL; unsigned int pidListAlloc = 0, pidListUsed = 0; static void print_usage(void); void ensurePidList(int nr) { if (pidListAlloc > nr) { return; } pidListAlloc = nr; pidList = realloc(pidList, pidListAlloc * sizeof(int)); } int main(int argn, char *args[]) { int ii = 1; // int verbose = FALSE; // Currently unused - squash warning int invert = 0; unsigned int max_pkts = (unsigned int)-1; const char *input_file = NULL, *output_file = NULL; if (argn < 2) { print_usage(); return 0; } ensurePidList(1024); while (ii < argn) { if (args[ii][0] == '-') { if (!strcmp("--help", args[ii]) || !strcmp("-h", args[ii]) || !strcmp("-help", args[ii])) { print_usage(); return 0; } else if (!strcmp("-verbose", args[ii]) || !strcmp("-v", args[ii])) { // verbose = TRUE; } else if (!strcmp("-m", args[ii]) || !strcmp("-max", args[ii])) { if (argn <= ii) { fprint_err("### tsfilter: -max requires an argument\n"); return 1; } max_pkts = atoi(args[ii+1]); ++ii; } else if (!strcmp("-!", args[ii]) || !strcmp("-invert", args[ii])) { invert = 1; } else if (!strcmp("-i", args[ii]) || !strcmp("-input", args[ii])) { if (argn <= ii) { fprint_err("### tsfilter: -input requires an argument\n"); return 1; } input_file = args[ii+1]; ++ii; } else if (!strcmp("-o", args[ii]) || !strcmp("-output", args[ii])) { if (argn <= ii) { fprint_err("### tsfilter: -output requires an argument\n"); return 1; } output_file = args[ii+1]; ++ii; } else { fprint_err("### tsfilter: " "Unrecognised command line switch '%s'\n", args[ii]); return 1; } } else { char *p = NULL; // It's a pid. if (pidListUsed >= pidListAlloc) { ensurePidList(pidListAlloc + 1024); } pidList[pidListUsed] = strtoul(args[ii], &p, 0); if (!(p && *p == '\0')) { fprint_err("### tsfilter: '%s' wasn't a valid number. \n", args[ii]); return 1; } ++pidListUsed; } ++ii; } if (!pidListUsed) { fprint_err("### tsfilter: No pids to filter. \n"); return 1; } // Now .. { int err; TS_reader_p tsreader; TS_writer_p tswriter; byte *pkt = NULL; unsigned int pid, pkt_num; int pusi, adapt_len, payload_len; byte *adapt, *payload; pkt_num = 0; err = open_file_for_TS_read((char *)input_file, &tsreader); if (err) { fprint_err("## tsfilter: Unable to open stdin for reading TS.\n"); return 1; } if (output_file) { err = tswrite_open(TS_W_FILE, (char *)output_file, NULL, 0, 1, &tswriter); } else { err = tswrite_open(TS_W_STDOUT, NULL, NULL, 0, 1, &tswriter); } if (err) { fprint_err("## tsfilter: Unable to open stdout for writing TS. \n"); return 1; } while (1) { err = read_next_TS_packet(tsreader, &pkt); if (err == EOF) { /* We're done */ break; } err = split_TS_packet(pkt, &pid, &pusi, &adapt, &adapt_len, &payload, &payload_len); if (err) { fprint_err("### Error splitting TS packet - continuing. \n"); } else { int i; int found = 0; for (i = 0 ;i < pidListUsed; ++i) { if (pid == pidList[i]) { ++found; // Yes! break; } } if (max_pkts != (unsigned int)-1 && pkt_num > max_pkts) { // We're done processing. If invert is on, // copy the rest of the output, otherwise quit. if (!invert) { break; } else { found = 0; } } // Invert the result, whatever it was. if (invert) { found = !found; } if (found) { // Write it out. err = tswrite_write(tswriter, pkt, pid, 0, 0); if (err) { fprint_err("### Error writing output - %d \n", err); return 2; } } ++pkt_num; } } // It's the end! tswrite_close(tswriter, 1); close_TS_reader(&tsreader); return 0; } } static void print_usage(void) { print_msg( "Usage: tsfilter [switches] ... \n" "\n"); REPORT_VERSION("tsfilter"); print_msg( "\n" " Filter the given pids out of stdin and write the result on stdout.\n" "\n" "Switches:\n" " -i Take input from this file and not stdin.\n" " -o Send output to this file and not stdout.\n" " -verbose, -v Be verbose.\n" " -max , -m All packets after the nth are regarded as\n" " not matching any pids.\n" " -!, -invert Invert whatever your decision was before \n" " applying it - the output contains only \n" " pids not in the list up to max packets \n" " and all packets in the input from then \n" " on.\n" ); } /* End file */ tstools-1.13~git20151030/tsinfo.c000066400000000000000000000272571261471605300163530ustar00rootroot00000000000000/* * Locate the PAT and PMT packets in an H.222 transport stream (TS), * and report on their contents (i.e., the program and stream info). * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ts_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "pidint_fns.h" #include "version.h" /* * Report on the program streams, by looking at the PAT and PMT packets * in the first `max` TS packets of the given input stream * * Returns 0 if all went well, 1 if something went wrong. */ static int report_streams(TS_reader_p tsreader, int max, int verbose) { int err; int ii; // TODO: Should really support multiple programs // (some use of pidint_list to support program number -> PMT?) pidint_list_p this_prog_list = NULL; pidint_list_p last_prog_list = NULL; pmt_p this_pmt = NULL; pmt_p last_pmt = NULL; uint32_t pmt_pid = 0; // which will get "masked" by the PAT pid byte *pat_data = NULL; int pat_data_len = 0; int pat_data_used = 0; byte *pmt_data = NULL; int pmt_data_len = 0; int pmt_data_used = 0; int num_pats = 0; int num_pmts = 0; fprint_msg("Scanning %d TS packets\n",max); for (ii=0; ii pat_data_used) continue; err = extract_prog_list_from_pat(verbose,pat_data,pat_data_len, &this_prog_list); if (err) { free_pidint_list(&last_prog_list); free_pmt(&last_pmt); if (pmt_data) free(pmt_data); free(pat_data); return err; } free(pat_data); pat_data = NULL; pat_data_len = 0; pat_data_used = 0; num_pats++; if (err) { free_pidint_list(&last_prog_list); free_pmt(&last_pmt); if (pmt_data) free(pmt_data); return err; } if (!same_pidint_list(this_prog_list,last_prog_list)) { if (last_prog_list != NULL) fprint_msg("\nPacket %d is PAT - content changed\n",ii+1); else if (!verbose) fprint_msg("\nPacket %d is PAT\n",ii+1); report_pidint_list(this_prog_list,"Program list","Program",FALSE); if (this_prog_list->length == 0) fprint_msg("No programs defined in PAT (packet %d)\n",ii+1); else { if (this_prog_list->length > 1) fprint_msg("Multiple programs in PAT - using the first\n"); pmt_pid = this_prog_list->pid[0]; } } free_pidint_list(&last_prog_list); last_prog_list = this_prog_list; } else if (pid == pmt_pid) { if (verbose) fprint_msg("Packet %d is PMT with PID %04x (%d)%s\n",ii+1,pid,pid, (payload_unit_start_indicator?"[pusi]":"")); if (payload_len == 0) { fprint_msg("Packet %d is PMT, but has no payload\n",ii+1); continue; } if (payload_unit_start_indicator && pmt_data) { // This is the start of a new PMT packet, but we'd already // started one, so throw its data away print_err("!!! Discarding previous (uncompleted) PMT data\n"); free(pmt_data); pmt_data = NULL; pmt_data_len = 0; pmt_data_used = 0; } else if (!payload_unit_start_indicator && !pmt_data) { // This is the continuation of a PMT packet, but we hadn't // started one yet print_err("!!! Discarding PMT continuation, no PMT started\n"); continue; } err = build_psi_data(verbose,payload,payload_len,pid, &pmt_data,&pmt_data_len,&pmt_data_used); if (err) { fprint_err("### Error %s PMT\n", (payload_unit_start_indicator?"starting new":"continuing")); free_pidint_list(&this_prog_list); free_pmt(&last_pmt); if (pmt_data) free(pmt_data); return 1; } // Do we need more data to complete this PMT? if (pmt_data_len > pmt_data_used) continue; err = extract_pmt(verbose,pmt_data,pmt_data_len,pid,&this_pmt); if (err) { free_pidint_list(&this_prog_list); free_pmt(&last_pmt); if (pmt_data) free(pmt_data); return err; } free(pmt_data); pmt_data = NULL; pmt_data_len = 0; pmt_data_used = 0; num_pmts++; if (same_pmt(this_pmt,last_pmt)) // Nothing to do { free_pmt(&this_pmt); continue; } if (last_pmt != NULL) fprint_msg("\nPacket %d is PMT with PID %04x (%d)" " - content changed\n",ii+1,pid,pid); else if (!verbose) fprint_msg("\nPacket %d is PMT with PID %04x (%d)\n",ii+1,pid,pid); report_pmt(TRUE," ",this_pmt); free_pmt(&last_pmt); last_pmt = this_pmt; } } fprint_msg("\nFound %d PAT packet%s and %d PMT packet%s in %d TS packets\n", num_pats,(num_pats==1?"":"s"), num_pmts,(num_pmts==1?"":"s"),max); free_pidint_list(&last_prog_list); free_pmt(&last_pmt); if (pmt_data) free(pmt_data); return 0; } static void print_usage() { print_msg( "Usage: tsinfo [switches] []\n" "\n" ); REPORT_VERSION("tsinfo"); print_msg( "\n" " Report on the program streams in a Transport Stream.\n" "\n" "Files:\n" " is an H.222 Transport Stream file (but see -stdin)\n" "\n" "Switches:\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdin Input from standard input, instead of a file\n" " -verbose, -v Output extra information about packets\n" " -max , -m Number of TS packets to scan. Defaults to 10000.\n" " -repeat Look for PMT packets, and report on each\n" ); } int main(int argc, char **argv) { int use_stdin = FALSE; char *input_name = NULL; int had_input_name = FALSE; int max = 10000; int verbose = FALSE; // True => output diagnostic/progress messages int lookfor = 1; int err = 0; TS_reader_p tsreader = NULL; int ii = 1; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-err",argv[ii])) { CHECKARG("tsinfo",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### tsinfo: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("tsinfo",ii); err = int_value("tsinfo",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-repeat",argv[ii])) { CHECKARG("tsinfo",ii); err = int_value("tsinfo",argv[ii],argv[ii+1],TRUE,10,&lookfor); if (err) return 1; ii++; } else if (!strcmp("-stdin",argv[ii])) { use_stdin = TRUE; had_input_name = TRUE; // so to speak } else { fprint_err("### tsinfo: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name) { fprint_err("### tsinfo: Unexpected '%s'\n",argv[ii]); return 1; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### tsinfo: No input file specified\n"); return 1; } err = open_file_for_TS_read((use_stdin?NULL:input_name),&tsreader); if (err) { fprint_err("### tsinfo: Unable to open input file %s for reading TS\n", use_stdin?"":input_name); return 1; } fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); err = report_streams(tsreader,max,verbose); if (err) { print_err("### tsinfo: Error reporting on stream\n"); (void) close_TS_reader(&tsreader); return 1; } err = close_TS_reader(&tsreader); if (err) return 1; return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tsplay.c000066400000000000000000000717111261471605300163570ustar00rootroot00000000000000/* * Play (stream) TS packets. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include // Sleeping and timing #include "compat.h" #include "printing_fns.h" #include "tsplay_fns.h" #include "tswrite_fns.h" #include "printing_fns.h" #include "misc_fns.h" #include "version.h" #include "ps_fns.h" #include "pes_fns.h" #include "pidint_fns.h" static void print_usage(int summary) { print_msg( "Basic usage: tsplay [:]\n" "\n" ); REPORT_VERSION("tsplay"); if (summary) print_msg( "\n" " Play the given file (containing Transport Stream or Program Stream\n" " data) 'at' the nominated host, or to an output file. The output\n" " is always Transport Stream.\n" ); else print_msg( "\n" " Reads from a file containing H.222.0 (ISO/IEC 13818-1) Transport\n" " Stream or Program Stream data (converting PS to TS as it goes),\n" " and 'plays' the Transport Stream 'at' the nominated host, or to an\n" " output file.\n" "\n" " Assumes a single program in the file, and for PS assumes that the\n" " program stream is well formed - i.e., that it starts with a pack\n" " header. A PS stream that ends after a PES packet, but without an\n" " MPEG_program_end_code will cause a warning message, but will not\n" " be treated as an error.\n" "\n" " Output to a file is only provided for testing purposes, and does\n" " not use the buffering/child process mechanisms (although this may\n" " not be clear from the informative messages output).\n" "\n" " Note that most switches can be placed anywhere on the command line.\n" ); print_msg( "\n" "Input:\n" " Input is from the named H.222 TS file.\n" " -stdin Input is from standard input.\n" "\n" "Output:\n" " \n" " : Normally, output is to a named host.\n" " If is not specified, it defaults to 88.\n" " Output defaults to UDP.\n" " -output \n" " -o Output is to file .\n" "\n" " -tcp Output to the host is via TCP.\n" " -udp Output to the host is via UDP (the default).\n" ); if (summary) print_msg( " -stdout Output is to standard output. Forces -quiet and -err stderr.\n" ); else print_msg( " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -stdout Output is to standard output. This does not make sense\n" " with -tcp or -udp. This forces -quiet and -err stderr.\n" ); print_msg( "\n" " -mcastif \n" " -i If output is via UDP, and is a multicast\n" " address, then is the IP address of the\n" " network interface to use. This may not be supported\n" " on some versions of Windows.\n" "\n" "General Switches:\n" " -quiet, -q Only output error messages\n" " -verbose, -v Output progress messages\n" " -help Show help on a particular subject\n" " -help Summarise the s that can be specified\n" "\n" ); if (summary) print_msg( " -max , -m Maximum number of TS/PS packets to read.\n" " See -details for more information.\n" " -loop Play the input file repeatedly. Can be combined\n" " with -max.\n" ); else fprint_msg( "Normal operation outputs some messages summarising the command line\n" "choices, information about the circular buffer filling, and\n" "confirmation when the program is ending.\n" "Quiet operation endeavours only to output error messages.\n" "Verbose operation outputs a progress message every %d TS packets,\n" "and a note whenever the input file is rewound for -loop.\n" "\n" " -loop Play the input file repeatedly.\n" "\n" "This assumes that the file ends neatly - i.e., not with a partial\n" " TS packet. On the other hand, it *can* be combined with -max to\n" "allow for short video sequences to be repeated.\n" "\n" "If PS data is being read, and looping has been selected, then the\n" "verbosity flags only apply to the first time through the data, and\n" "thereafter it is as if -quiet had been specified.\n" "\n" " -max , -m Maximum number of TS packets to read\n" " (or PS packets if the input data is PS)\n" "\n" "Note that the -max value must be at least 7 * , since\n" "the parent process adds TS packets to the circular buffer 7 at a\n" "time, and the child process waits until the buffer has filled up\n" "before doing anything. In recognition of this, if the user specifies\n" "a smaller value, the software will exit with a complaint.\n" "\n" "(Strictly, if the input is PS, this restriction is not correct, as\n" "one PS packet generates multiple TS packets, and thus lower values\n" "for -max could be allowed. For simplicity, this is not considered.)\n" "", TSPLAY_REPORT_EVERY); } static void print_help_help() { print_msg( "With no switches, tsplay will give a brief summary of its basic usage.\n" "Otherwise:\n" "\n" " -help detail[s] Show an expanded version of the help you get if you\n" " run tsplay with no arguments\n" " -help ts Show help specific to playing TS data\n" " -help ps Show help specific to playing PS data\n" " -help tuning Show help about how to tune the program's operation\n" " -help test[ing] Show help on switches that can be useful in testing\n" " the target application (the video player)\n" " -help debug[ging] Show help on debugging this application.\n" " -help Show this message\n" " -help all Show all available help (equivalent to each of the\n" " above specific helps, in order)\n" ); } static void print_help_ts() { print_msg( "Transport Stream Switches:\n" "The following switches are only applicable if the input data is TS.\n" "\n" " -ignore Any TS packets with this PID will not be output\n" " (more accurately, any TS packets with this PID and with\n" " PCR information will be transmitted with PID 0x1FFF, and\n" " any other packets with this PID will be ignored).\n" " The may not be 0, and should not be the PAT or PMT.\n" "\n" "Normally, the TS reading process remembers the last PCR, and also scans ahead\n" "for the next PCR. This allows it to calculate an accurate timestamp for each TS\n" "packet, at the cost of ignoring any packets before the first PCR, and also\n" "(at least in the currrent implementation), ignoring any TS packets after the\n" "last PCR, and also at the cost of some extra copying of data.\n" "\n" "However, sometimes it is more appropriate to revert to the mechanism used by\n" "earlier versions of tsplay:\n" "\n" " -oldpace Switch off scanning ahead for the next PCR.\n" " -pace-pcr1 v1 of the PCR scan code\n" " -pace-pcr2-ts v2 of the PCR scan code - use 1st PCR PID found [default]\n" " -pace-pcr2-pmt v2 of the PCR scan code - get PCR PID from PMT\n" "\n" "which attempts to predict an approximate PCR for each TS packet, based on an\n" "initial speed (see '-bitrate'/'-byterate' in '-help tuning') and the PCRs found\n" "earlier in the data stream. This works reasonably well for streams with a\n" "constant bitrate, but does not cope well if the bitrate varies greatly.\n" "\n" "Note that '-nopcrs' (see '-help tuning') also implies '-oldpace'.\n" "\n" "In order to buffer PCRs, the first PCR must be found. Normally this is done\n" "by finding the first PAT/PMT, and reading the PCR PID from there. However,\n" "sometimes it is useful to *tell* the program where to look for its first PCR:\n" "\n" " -forcepcr Specifies which PID to look for to find the first PCR.\n" "\n" "Note that after the first PCR is read, *all* TS packets are inspected for\n" "PCRs, irrespective of PID.\n" ); } static void print_help_ps() { print_msg( "Program Stream Switches:\n" "The following switches are only applicable if the input data is PS.\n" "\n" "If input is from a file, then the program will look at the start of\n" "the file to determine if the stream is H.264 or H.262 data. This\n" "process may occasionally come to the wrong conclusion, in which case\n" "the user can override the choice:\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" "\n" "Program stream data from a DVD stores its audio differently\n" "(specifically, the DVD specification states that AC-3 (Dolby), DTS and\n" "other audio are stored in a specialised manner in private_stream_1):\n" "\n" " -dvd The PS data is from a DVD. This is the default.\n" " This switch has no effect on MPEG-1 PS data.\n" " -notdvd, -nodvd The PS data is not from a DVD.\n" "\n" "Specifying which audio/video streams to read:\n" "\n" " -vstream Take video from video stream (0..7).\n" " The default is the first video stream found.\n" " -astream Take audio from audio stream (0..31).\n" " The default is the first audio stream found\n" " (this includes private_stream_1 on non-DVD streams).\n" " -ac3stream Take audio from AC3 substream (0..7), from\n" " private_stream_1. This implies -dvd.\n" " (If audio is being taken from a substream, the user\n" " is assumed to have determined which one is wanted,\n" " e.g., using psreport)\n" "\n" "The input PS data does not have PAT/PMT or PID values, and thus tsplay\n" "must invent them. The following switches may be used to choose particular\n" "PID values:\n" "\n" " -vpid is the PID to output video data with.\n" " Use '-vpid 0x' to specify a hex value.\n" " Defaults to 0x68.\n" " -apid is the PID to output audio data with.\n" " Use '-apid 0x' to specify a hex value.\n" " Defaults to 0x67.\n" " -pmt is the PID to output PMT data with.\n" " Use '-pmt 0x' to specify a hex value.\n" " Defaults to 0x66.\n" "\n" "The default values for the various PIDs should suffice in most cases\n" "\n" "If the audio stream being output is Dolby (AC-3), then the stream type\n" "used to output it differs for DVB (European) and ATSC (USA) data. It\n" "may be specified as follows:\n" "\n" " -dolby dvb Use stream type 0x06 (the default)\n" " -dolby atsc Use stream type 0x81\n" "\n" "Finally, it is occasionally useful to tweak how often PAT/PMT are written,\n" "and how much padding the stream starts with:\n" "\n" " -prepeat Output the program data (PAT/PMT) after every \n" " PS packs, to allow a TS reader to resynchronise\n" " if it starts reading part way through the stream.\n" " Defaults to 100.\n" " -pad Pad the start of the output stream with filler\n" " TS packets, to allow a TS reader time to byte align\n" " with the datastream before any significant data\n" " occurs. Defaults to 8.\n" "\n" ); } static void print_help_tuning() { tswrite_help_tuning(); } static void print_help_testing() { tswrite_help_testing(); print_msg( "\n" " -drop As TS packets are output, for every + packets,\n" " keep and then drop (throw away) .\n" " This can be useful when testing other applications.\n" ); } static void print_help_debugging() { tswrite_help_debug(); } int main(int argc, char **argv) { TS_writer_p tswriter; struct TS_context context; char *input_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; int input = -1; int max = 0; // The maximum number of TS packets to read (or 0) int quiet = FALSE; int verbose = FALSE; int err = 0; int ii = 1; int loop = FALSE; time_t start,end; int is_TS; // Does it appear to be TS or PS? // Values relevent to "opening" the output file/socket enum TS_writer_type how = TS_W_UNDEFINED; // how to output our TS data char *output_name = NULL; // the output filename/host int port = 88; // the port to connect to char *multicast_if = NULL; // IP address of multicast i/f tsplay_output_pace_mode pace_mode = TSPLAY_OUTPUT_PACE_PCR2_TS; uint32_t pid_to_ignore = 0; uint32_t override_pcr_pid = 0; // 0 means "use the PCR found in the PMT" // Program Stream specific options uint32_t pmt_pid = 0x66; uint32_t video_pid = 0x68; uint32_t pcr_pid = video_pid; // Use PCRs from the video stream uint32_t audio_pid = 0x67; int repeat_program_every = 100; int pad_start = 8; int input_is_dvd = TRUE; int video_stream = -1; int audio_stream = -1; int want_ac3_audio = FALSE; int want_h262 = TRUE; int force_stream_type = FALSE; int want_dolby_as_dvb = TRUE; int drop_packets = 0; int drop_number = 0; if (argc < 2) { print_usage(TRUE); return 0; } // Process the standard tswrite switches/arguments err = tswrite_process_args("tsplay",argc,argv,&context); if (err) return 1; // And process any remaining arguments... while (ii < argc) { // Ignore any arguments that have already been "eaten" if (!strcmp(argv[ii],TSWRITE_PROCESSED)) { ii++; continue; } if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { if (argc == (ii+1)) print_help_help(); else { if (!strcmp(argv[ii+1],"ps")) print_help_ps(); else if (!strcmp(argv[ii+1],"ts")) print_help_ts(); else if (!strcmp(argv[ii+1],"tuning")) print_help_tuning(); else if (!strcmp(argv[ii+1],"test") || !strcmp(argv[ii+1],"testing")) print_help_testing(); else if (!strcmp(argv[ii+1],"debug") || !strcmp(argv[ii+1],"debugging")) print_help_debugging(); else if (!strcmp(argv[ii+1],"detail") || !strcmp(argv[ii+1],"details")) print_usage(FALSE); else if (!strcmp(argv[ii+1],"all")) { print_usage(FALSE); print_msg("\n"); print_help_ts(); print_msg("\n"); print_help_ps(); print_msg("\n"); print_help_tuning(); print_msg("\n"); print_help_testing(); print_msg("\n"); print_help_debugging(); print_msg("\n"); print_help_help(); } else { fprint_err("### tsplay: " "Unrecognised command line switch '%s %s' -- try '-help'\n", argv[ii],argv[ii+1]); return 1; } } return 0; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { quiet = TRUE; verbose = FALSE; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { quiet = FALSE; verbose = TRUE; } else if (!strcmp("-output",argv[ii]) || !strcmp("-o",argv[ii])) { CHECKARG("tsplay",ii); had_output_name = TRUE; how = TS_W_FILE; output_name = argv[ii+1]; ii++; } else if (!strcmp("-mcastif",argv[ii]) || !strcmp("-i",argv[ii])) { CHECKARG("tsplay",ii); multicast_if = argv[ii+1]; ii++; } else if (!strcmp("-stdout",argv[ii])) { had_output_name = TRUE; // more or less how = TS_W_STDOUT; output_name = NULL; redirect_output_stderr(); } else if (!strcmp("-stdin",argv[ii])) { had_input_name = TRUE; // more or less input_name = NULL; } else if (!strcmp("-err",argv[ii])) { CHECKARG("tsplay",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### tsplay: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-tcp",argv[ii])) { if (how == TS_W_STDOUT || how == TS_W_FILE) { print_err("### tsplay: -tcp does not make sense with file output\n"); return 1; } how = TS_W_TCP; } else if (!strcmp("-udp",argv[ii])) { if (how == TS_W_STDOUT || how == TS_W_FILE) { print_err("### tsplay: -udp does not make sense with file output\n"); return 1; } how = TS_W_UDP; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("tsplay",ii); err = int_value("tsplay",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-oldpace",argv[ii])) { pace_mode = TSPLAY_OUTPUT_PACE_FIXED; } else if (!strcmp("-pace-pcr1",argv[ii])) { pace_mode = TSPLAY_OUTPUT_PACE_PCR1; } else if (!strcmp("-pace-pcr2-ts",argv[ii])) { pace_mode = TSPLAY_OUTPUT_PACE_PCR2_TS; } else if (!strcmp("-pace-pcr2-pmt",argv[ii])) { pace_mode = TSPLAY_OUTPUT_PACE_PCR2_PMT; } else if (!strcmp("-forcepcr",argv[ii])) { CHECKARG("tsplay",ii); err = unsigned_value("tsplay",argv[ii],argv[ii+1],0,&override_pcr_pid); if (err) return 1; ii++; } else if (!strcmp("-loop",argv[ii])) { loop = TRUE; } else if (!strcmp("-avc",argv[ii]) || !strcmp("-h264",argv[ii])) { force_stream_type = TRUE; want_h262 = FALSE; } else if (!strcmp("-h262",argv[ii])) { force_stream_type = TRUE; want_h262 = TRUE; } else if (!strcmp("-dvd",argv[ii])) { input_is_dvd = TRUE; } else if (!strcmp("-notdvd",argv[ii]) || !strcmp("-nodvd",argv[ii])) { input_is_dvd = FALSE; } else if (!strcmp("-vstream",argv[ii])) { CHECKARG("tsplay",ii); err = int_value_in_range("ps2ts",argv[ii],argv[ii+1],0,0xF,0, &video_stream); if (err) return 1; ii++; } else if (!strcmp("-astream",argv[ii])) { CHECKARG("tsplay",ii); err = int_value_in_range("ps2ts",argv[ii],argv[ii+1],0,0x1F,0, &audio_stream); if (err) return 1; want_ac3_audio = FALSE; ii++; } else if (!strcmp("-ac3stream",argv[ii])) { CHECKARG("tsplay",ii); err = int_value_in_range("ps2ts",argv[ii],argv[ii+1],0,0x7,0, &audio_stream); if (err) return 1; want_ac3_audio = TRUE; input_is_dvd = TRUE; ii++; } else if (!strcmp("-dolby",argv[ii])) { CHECKARG("tsplay",ii); if (!strcmp("dvb",argv[ii+1])) want_dolby_as_dvb = TRUE; else if (!strcmp("atsc",argv[ii+1])) want_dolby_as_dvb = FALSE; else { print_err("### tsplay: -dolby must be followed by dvb or atsc\n"); return 1; } ii++; } else if (!strcmp("-prepeat",argv[ii])) { CHECKARG("tsplay",ii); err = int_value("tsplay",argv[ii],argv[ii+1],TRUE,10, &repeat_program_every); if (err) return 1; ii++; } else if (!strcmp("-pad",argv[ii])) { CHECKARG("tsplay",ii); err = int_value("tsplay",argv[ii],argv[ii+1],TRUE,10,&pad_start); if (err) return 1; ii++; } else if (!strcmp("-ignore",argv[ii])) { CHECKARG("tsplay",ii); err = unsigned_value("tsplay",argv[ii],argv[ii+1],0,&pid_to_ignore); if (err) return 1; if (pid_to_ignore == 0) { print_err("### tsplay: -ignore 0 is not allowed\n"); return 1; } ii++; } else if (!strcmp("-vpid",argv[ii])) { CHECKARG("tsplay",ii); err = unsigned_value("tsplay",argv[ii],argv[ii+1],0,&video_pid); if (err) return 1; ii++; } else if (!strcmp("-apid",argv[ii])) { CHECKARG("tsplay",ii); err = unsigned_value("tsplay",argv[ii],argv[ii+1],0,&audio_pid); if (err) return 1; ii++; } else if (!strcmp("-pmt",argv[ii])) { CHECKARG("tsplay",ii); err = unsigned_value("tsplay",argv[ii],argv[ii+1],0,&pmt_pid); if (err) return 1; ii++; } else if (!strcmp("-drop",argv[ii])) { if (ii+2 >= argc) { print_err("### tsplay: -drop requires two arguments\n"); return 1; } err = int_value("tsplay",argv[ii],argv[ii+1],TRUE,0,&drop_packets); if (err) return 1; err = int_value("tsplay",argv[ii],argv[ii+2],TRUE,0,&drop_number); if (err) return 1; ii += 2; } else { fprint_err("### tsplay: " "Unrecognised command line switch '%s' -- try '-help'\n",argv[ii]); return 1; } } else { if (!had_input_name) { input_name = argv[ii]; had_input_name = TRUE; } else if (!had_output_name) { // This is presumably the host to write to err = host_value("tsplay",NULL,argv[ii],&output_name,&port); if (err) return 1; had_output_name = TRUE; if (how == TS_W_UNDEFINED) how = TS_W_UDP; } else { fprint_err("### tsplay: Unexpected '%s'\n",argv[ii]); return 1; } } ii++; } if (!had_input_name) { print_err("### tsplay: No input file specified\n"); return 1; } // We *need* some output... if (!had_output_name) { print_err("### tsplay: No output file or host specified\n"); return 1; } // On the other hand, it can be nice to have a *string* for if (how == TS_W_STDOUT) output_name = ""; // Try to stop extraneous data ending up in our output stream if (how == TS_W_STDOUT) { verbose = FALSE; quiet = TRUE; } // This is an important check if (max > 0 && how == TS_W_UDP && (max / 7) < context.circ_buf_size) { fprint_err("### tsplay: -max %d cannot work with -buffer %d" " - max must be at least %d",max, context.circ_buf_size,context.circ_buf_size*7); if (max/7 > 0) fprint_err(",\n or buffer size reduced to %d",max/7); print_err("\n"); return 1; } // If tswrite found '-nopcrs' in the switches, make sure that we've // switched PCR lookahead off. if (context.pcr_mode == TSWRITE_PCR_MODE_NONE) pace_mode = TSPLAY_OUTPUT_PACE_FIXED; else if (pace_mode == TSPLAY_OUTPUT_PACE_PCR1) context.pcr_mode = TSWRITE_PCR_MODE_PCR1; if (input_name) { input = open_binary_file(input_name,FALSE); if (input == -1) { fprint_err("### tsplay: Unable to open input file %s\n",input_name); return 1; } err = determine_if_TS_file(input,&is_TS); if (err) { fprint_err("### tsplay: Cannot play file %s\n",output_name); (void) close_file(input); return 1; } } else { input_name = ""; input = STDIN_FILENO; is_TS = TRUE; // an assertion } if (!quiet) fprint_msg("Reading from %s%s\n",input_name,(loop?" (and looping)":"")); err = tswrite_open(how,output_name,multicast_if,port,quiet, &tswriter); if (err) { fprint_err("### tsplay: Cannot open/connect to %s\n",output_name); (void) close_file(input); return 1; } if (!quiet) { if (is_TS) { print_msg("Input appears to be Transport Stream\n"); if (pace_mode != TSPLAY_OUTPUT_PACE_FIXED) print_msg("Using 'exact' TS packet timing (by looking-ahead to the next PCR)\n"); else print_msg("Approximating/predicting intermediate PCRs\n"); if (pid_to_ignore) fprint_msg("Ignoring PID %04x (%d)\n",pid_to_ignore,pid_to_ignore); } else { print_msg("Input appears to be Program Stream\n"); if (input_is_dvd) print_msg("Treating input as from DVD\n"); else print_msg("Treating input as NOT from DVD\n"); } if (max) fprint_msg("Stopping after at most %d packets\n",max); if (how == TS_W_UDP) tswrite_report_args(&context); } if (drop_packets) { if (!quiet) fprint_msg("DROPPING: Keeping %d TS packet%s, then dropping (throwing away) %d\n", drop_packets,(drop_packets==1?"":"s"),drop_number); tswriter->drop_packets = drop_packets; tswriter->drop_number = drop_number; } if (!quiet) start = time(NULL); // We can only use buffered output for TCP/IP and UDP // (it doesn't make much sense for output to a file) if (how == TS_W_UDP) { err = tswrite_start_buffering_from_context(tswriter,&context); if (err) { print_err("### tsplay: Error setting up buffering\n"); (void) close_file(input); (void) tswrite_close(tswriter,TRUE); return 1; } } if (is_TS) { err = play_TS_stream(input,tswriter,pace_mode,pid_to_ignore, override_pcr_pid,max,loop,quiet,verbose); } else err = play_PS_stream(input,tswriter,pad_start, repeat_program_every,force_stream_type, want_h262,input_is_dvd, video_stream,audio_stream,want_ac3_audio, want_dolby_as_dvb,pmt_pid,pcr_pid, video_pid,TRUE,audio_pid,max,loop, verbose,quiet); if (err) { print_err("### tsplay: Error playing stream\n"); (void) close_file(input); (void) tswrite_close(tswriter,TRUE); return 1; } if (!quiet) { end = time(NULL); fprint_msg("Started output at %s",ctime(&start)); fprint_msg("Finished output at %s",ctime(&end)); fprint_msg("Elapsed time %.1fs\n",difftime(end,start)); } err = close_file(input); if (err) { fprint_err("### tsplay: Error closing input file %s\n",input_name); (void) tswrite_close(tswriter,TRUE); return 1; } err = tswrite_close(tswriter,quiet); if (err) { fprint_err("### tsplay: Error closing output to %s\n",output_name); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tsplay_defns.h000066400000000000000000000032101261471605300175300ustar00rootroot00000000000000/* * Support for playing (streaming) TS packets. * * Exposes the functionality in tsplay_innards.c, mainly for use by tsplay.c * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _tsplay_defns #define _tsplay_defns // If not being quiet, report progress every TSPLAY_REPORT_EVERY packets read #define TSPLAY_REPORT_EVERY 10000 typedef enum tsplay_output_pace_mode_e { TSPLAY_OUTPUT_PACE_FIXED, TSPLAY_OUTPUT_PACE_PCR1, // Src buffering timing TSPLAY_OUTPUT_PACE_PCR2_TS, // write buffer timing - use 1st PCR found for PID TSPLAY_OUTPUT_PACE_PCR2_PMT // write buffer timing = look up PCR PID in PMT } tsplay_output_pace_mode; #endif // tsplay_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tsplay_fns.h000066400000000000000000000135541261471605300172330ustar00rootroot00000000000000/* * Support for playing (streaming) TS packets. * * Exposes the functionality in tsplay_innards.c, mainly for use by tsplay.c * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _tsplay_fns #define _tsplay_fns #include "tswrite_defns.h" #include "tsplay_defns.h" /* * Read TS packets and then output them. * * Assumes (strongly) that it is starting from the start of the file. * * - `input` is the input stream (descriptor) to read * - `tswriter` is our (maybe buffered) writer * - if `pid_to_ignore` is non-zero, then any TS packets with that PID * will not be written out (note: any PCR information in them may still * be used) * - if `scan_for_PCRs`, use a read-ahead buffer to find the *next* PCR, * and thus allow exact timing of packets. * - if we are using the PCR read-ahead buffer, and `override_pcr_pid` is * non-zero, then it is the PID to use for PCRs, ignoring any value found in * a PMT * - if `max` is greater than zero, then at most `max` TS packets should * be read from the input * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) * - if `quiet` is true, then only error messages should be written out * - if `verbose` is true, then give extra progress messages * * Returns 0 if all went well, 1 if something went wrong. */ extern int play_TS_stream(int input, TS_writer_p tswriter, const tsplay_output_pace_mode pace_mode, uint32_t pid_to_ignore, uint32_t override_pcr_pid, int max, int loop, int quiet, int verbose); /* * Read PS packets and then output them as TS. * * - `input` is the program stream * - `output` is the transport stream * - `pad_start` is the number of filler TS packets to start the output * with. * - `program_repeat` is how often (after how many PS packs) to repeat * the program information (PAT/PMT) * - `want_h264` should be true to indicate that the video stream is H.264 * (ISO/IEC 14496-2, MPEG-4/AVC), false if it is H.262 (ISO/IEC 13818-3, * MPEG-2, or indeed 11172-3, MPEG-1) * - `input_is_dvd` indicates if the PS data came from a DVD, and thus follows * its conventions for private_stream_1 and AC-3/DTS/etc. substreams * - `video_stream` indicates which video stream we want - i.e., the stream * with id 0xE0 + - and -1 means the first video stream found. * - `audio_stream` indicates which audio stream we want. If `want_ac3_audio` * is false, then this will be the stream with id 0xC0 + , or, * if it is -1, the first audio stream found. * - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want * audio from private_stream_1 (0xBD) with substream id , * otherwise we ignore `audio_stream` and assume that all data in * private_stream_1 is the audio we want. * - `want_dolby_as_dvb` indicates if any Dolby (AC-3) audio data should be output * with DVB or ATSC stream type * - `pmt_pid` is the PID of the PMT to write * - `pcr_pid` is the PID of the TS unit containing the PCR * - `video_pid` is the PID for the video we write * - `keep_audio` is true if the audio stream should be output, false if * it should be ignored * - `audio_pid` is the PID for the audio we write * - if `max` is non-zero, then we want to stop reading after we've read * `max` packets * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) * - if `verbose` then we want to output diagnostic information * (nb: only applies to first time if looping is enabled) * - if `quiet` then we want to be as quiet as we can * (nb: only applies to first time if looping is enabled) * * Returns 0 if all went well, 1 if something went wrong. */ extern int play_PS_stream(int input, TS_writer_p output, int pad_start, int program_repeat, int force_stream_type, int want_h262, int input_is_dvd, int video_stream, int audio_stream, int want_ac3_audio, int want_dolby_as_dvb, uint32_t pmt_pid, uint32_t pcr_pid, uint32_t video_pid, int keep_audio, uint32_t audio_pid, int max, int loop, int verbose, int quiet); #endif // tsplay_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tsplay_innards.c000066400000000000000000000647011261471605300200760ustar00rootroot00000000000000/* * This is the core functionality used by tsplay to play (stream) TS packets. * * It is abstracted here so that it can be used in other contexts. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include // Sleeping and timing #include "compat.h" #include "printing_fns.h" #include "ts_fns.h" #include "ps_fns.h" #include "pes_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "tsplay_fns.h" #include "tswrite_fns.h" #include "pidint_fns.h" // ============================================================ // Common TS packet reading code // ============================================================ /* * Read the next TS packet, coping with looping, etc. * * - `tsreader` is the TS reader context * - `count` is a running count of TS packets read from this input * - `data` is a pointer to the data for the packet * - `pid` is the PID of the TS packet * - `got_PCR` is TRUE if the adaptation field of this packet contains a PCR * - `pcr` is then the PCR value itself * - if `max` is greater than zero, then at most `max` TS packets should * be read from the input * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) - i.e., rewind to `start_posn` and start again if * `count` reaches `max` (obviously only if `max` is greater than zero). * - `start_count` is the value `count` should have after we've looped back * to `start_posn` * - if `quiet` is true, then only error messages should be written out * * Returns 0 if all went well, 1 if something went wrong, EOF if `loop` is * false and either EOF was read, or `max` TS packets were read. */ static int read_TS_packet(TS_reader_p tsreader, uint32_t *count, byte *data[TS_PACKET_SIZE], uint32_t *pid, int *got_pcr, uint64_t *pcr, int max, int loop, offset_t start_posn, uint32_t start_count, int quiet) { int err; int payload_unit_start_indicator; byte *adapt; int adapt_len; byte *payload; int payload_len; if (max > 0 && (*count) >= (uint32_t)max) { if (loop) { if (!quiet) fprint_msg("Read %d packets, rewinding and continuing\n",max); err = seek_using_TS_reader(tsreader,start_posn); if (err) return 1; *count = start_count; } else { if (!quiet) fprint_msg("Stopping after %d TS packets\n",max); return EOF; } } // Read the next packet while ((err = read_next_TS_packet(tsreader,data)) != 0) { if (err == EOF) { if (!loop) return EOF; if (!quiet) fprint_msg("EOF (after %d TS packets), rewinding and continuing\n", *count); } else { fprint_err("### Error reading TS packet %d\n",*count); if (!loop) return 1; if (!quiet) print_msg("!!! Rewinding and continuing anyway\n"); } err = seek_using_TS_reader(tsreader,start_posn); if (err) return 1; *count = start_count; } err = split_TS_packet(*data,pid,&payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err) { fprint_err("### Error splitting TS packet %d\n",*count); return 1; } get_PCR_from_adaptation_field(adapt,adapt_len,got_pcr,pcr); (*count) ++; return 0; } /* * Read TS packets until we have found the PCR PID for our program stream, * outputting packets (without using their PCR) as we go. * * - `tsreader` is the TS reader context * - `tswriter` is our (buffered) writer * - `pcr_pid` is the PID containing PCRs as indicated by the PMT * - `num_read` is how many TS packets we read * - if `max` is greater than zero, then at most `max` TS packets should * be read from the input * - if `quiet` is true, then only error messages should be written out * * Returns 0 if all went well, 1 if something went wrong. */ static int find_PCR_PID(TS_reader_p tsreader, TS_writer_p tswriter, uint32_t *pcr_pid, uint32_t *num_read, int max, int quiet) { int err; int count = 0; byte *data; uint32_t pid; int payload_unit_start_indicator; byte *adapt; int adapt_len; byte *payload; int payload_len; int got_PAT = FALSE; pidint_list_p prog_list = NULL; pmt_p pmt = NULL; uint32_t pmt_pid = 0; // safe initial value byte *pat_data = NULL; int pat_data_len = 0; int pat_data_used = 0; byte *pmt_data = NULL; int pmt_data_len = 0; int pmt_data_used = 0; int pmt_program_number = -1; for (;;) { err = read_next_TS_packet(tsreader,&data); if (err == EOF) { fprint_err("### EOF (after %d TS packets), before finding program" " information\n",count); if (pmt_data) free(pmt_data); return 1; } else if (err) { fprint_err("### Error reading TS packet %d\n",count+1); if (pmt_data) free(pmt_data); return 1; } count++; err = split_TS_packet(data,&pid,&payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err) { fprint_err("### Error splitting TS packet %d\n",count); if (pmt_data) free(pmt_data); return 1; } // Whatever we've found, don't forget to write it out via the // circular buffer (and we *know* it doesn't have a PCR that is // useful to us, as yet) err = tswrite_write(tswriter,data,pid,FALSE,0); if (err) { fprint_err("### Error writing TS packet %d to circular buffer\n", count); if (pmt_data) free(pmt_data); return 1; } if (pid == 0x0000 && !got_PAT) { if (!quiet) fprint_msg("Packet %d is PAT\n",count); if (payload_unit_start_indicator && pat_data) { // This is the start of a new PAT packet, but we'd already // started one, so throw its data away print_err("!!! Discarding previous (uncompleted) PAT data\n"); free(pat_data); pat_data = NULL; pat_data_len = 0; pat_data_used = 0; } else if (!payload_unit_start_indicator && !pat_data) { // This is the continuation of a PAT packet, but we hadn't // started one yet print_err("!!! Discarding PAT continuation, no PAT started\n"); continue; } err = build_psi_data(FALSE,payload,payload_len,pid, &pat_data,&pat_data_len,&pat_data_used); if (err) { fprint_err("### Error %s PAT\n", (payload_unit_start_indicator?"starting new":"continuing")); if (pat_data) free(pat_data); continue; } // Do we need more data to complete this PAT? if (pat_data_len > pat_data_used) continue; err = extract_prog_list_from_pat(FALSE,pat_data,pat_data_len,&prog_list); if (err != 0) { free(pat_data); continue; } if (!quiet) report_pidint_list(prog_list,"Program list","Program",FALSE); if (prog_list->length > 1 && !quiet) print_msg("Multiple programs in PAT - using the first\n\n"); pmt_pid = prog_list->pid[0]; pmt_program_number = prog_list->number[0]; got_PAT = TRUE; free_pidint_list(&prog_list); free(pat_data); pat_data = NULL; pat_data_len = 0; pat_data_used = 0; } else if (got_PAT && pid == pmt_pid) { if (!quiet) fprint_msg("Packet %d %s PMT with PID %04x\n", count, payload_unit_start_indicator?"starts":"continues", pmt_pid); if (payload_unit_start_indicator && pmt_data) { // This is the start of a new PMT packet, but we'd already // started one, so throw its data away print_err("!!! Discarding previous (uncompleted) PMT data\n"); free(pmt_data); pmt_data = NULL; pmt_data_len = 0; pmt_data_used = 0; } else if (!payload_unit_start_indicator && !pmt_data) { // This is the continuation of a PMT packet, but we hadn't // started one yet print_err("!!! Discarding PMT continuation, no PMT started\n"); continue; } err = build_psi_data(FALSE,payload,payload_len,pid, &pmt_data,&pmt_data_len,&pmt_data_used); if (err) { fprint_err("### Error %s PMT\n", (payload_unit_start_indicator?"starting new":"continuing")); if (pmt_data) free(pmt_data); return 1; } // Do we need more data to complete this PMT? if (pmt_data_len > pmt_data_used) continue; err = extract_pmt(FALSE,pmt_data,pmt_data_len,pmt_pid,&pmt); free(pmt_data); pmt_data = NULL; if (err) return err; if (pmt->program_number != pmt_program_number) { if (!quiet) fprint_msg("Discarding PMT program %d - looking for %d\n", pmt->program_number, pmt_program_number); free_pmt(&pmt); continue; } if (!quiet) report_pmt(TRUE," ",pmt); *pcr_pid = pmt->PCR_pid; free_pmt(&pmt); if (!quiet) fprint_msg("Taking timing information from PID 0x%03x\n",*pcr_pid); *num_read = count; return 0; } if (max > 0 && count >= max) { fprint_err("### Stopping after %d TS packets, before finding program" " information\n",max); if (pmt_data) free(pmt_data); return 1; } } } // ============================================================ // Play the TS data // ============================================================ /* * Read TS packets and then output them, using the buffered approach * so that we read-ahead to get the next PCR, and thus have reliable * timing information. * * Assumes (strongly) that it is starting from the start of the file. * * - `tsreader` is the TS reader context * - `tswriter` is our (maybe buffered) writer * - if `pid_to_ignore` is non-zero, then any TS packets with that PID * will not be written out (note: any PCR information in them may still * be used) * - if `override_pcr_pid` is non-zero, then it is the PID to use for PCRs, * ignoring any value found in a PMT * - if `max` is greater than zero, then at most `max` TS packets should * be read from the input * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) * - if `quiet` is true, then only error messages should be written out * - if `verbose` is true, then give extra progress messages * * Returns 0 if all went well, 1 if something went wrong. */ static int play_buffered_TS_packets(TS_reader_p tsreader, TS_writer_p tswriter, uint32_t pid_to_ignore, uint32_t override_pcr_pid, int max, int loop, int quiet, int verbose) { int err; int total = 0; uint32_t count = 0; uint32_t pcr_pid; uint32_t start_count = 0; // which TS packet to loop from offset_t start_posn = 0; // These are only used in the loop below, but the compiler grumbles if // they're uninitialised (it isn't sure if they're being set by the call // to read_buffered_TS_packet() or not). I don't want to have to keep // thinking about the compiler warning, but I also know that these values // *will* be set by the function, so I don't want them reinitialised // every time round the loop. So hoist them back up to here... byte *data = NULL; uint32_t pid = 0; uint64_t pcr = 0; // Before we can use PCRs for timing, we need to read a PMT which tells us // what our video stream is (so we can get our PCRs therefrom). if (override_pcr_pid) { pcr_pid = override_pcr_pid; if (!quiet) fprint_msg("Forcing use of PCR PID 0x%03x (%d)\n",pcr_pid,pcr_pid); } else { err = find_PCR_PID(tsreader,tswriter,&pcr_pid,&start_count,max,quiet); if (err) { fprint_err("### Unable to find PCR PID for timing information\n" " Looked in first %d TS packets\n",max); return 1; } } // Once we've found that, we're ready to play our data err = prime_read_buffered_TS_packet(tsreader,pcr_pid); if (err) return 1; // If we're looping, remember the location of the first packet of (probable) // data - there's not much point rewinding before that point if (loop) start_posn = start_count * TS_PACKET_SIZE; count = start_count; for (;;) { err = read_buffered_TS_packet(tsreader,&count,&data,&pid,&pcr, max,loop,start_posn,start_count,quiet); if (err == EOF) // shouldn't occur if `loop` break; else if (err) { if (tsreader->file != STDIN_FILENO) { fprint_err("### Last TS packet read was at " LLU_FORMAT "\n", (uint64_t)count * TS_PACKET_SIZE); } return 1; } total ++; // If we've been asked to ignore this packet, we should be able to // just ignore it -- since all TS packets have their time associated // with them, we shouldn't need to send a "dummy" packet, just in // case it had time on it. if (pid_to_ignore != 0 && pid == pid_to_ignore) continue; // And write it out via the circular buffer err = tswrite_write(tswriter,data,pid,TRUE,pcr); if (err) { fprint_err("### Error writing TS packet %d to circular buffer\n", count); return 1; } if (!quiet && verbose && total%TSPLAY_REPORT_EVERY == 0) fprint_msg("Transferred %d TS packets\n",total); } if (!quiet) fprint_msg("Transferred %d TS packet%s in total\n",total,(total==1?"":"s")); return 0; } /* * Read TS packets and then output them. * * Assumes (strongly) that it is starting from the start of the file. * * - `tsreader` is the TS reader context * - `tswriter` is our (maybe buffered) writer * - if `pid_to_ignore` is non-zero, then any TS packets with that PID * will not be written out (note: any PCR information in them may still * be used) * - if `max` is greater than zero, then at most `max` TS packets should * be read from the input * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) * - if `quiet` is true, then only error messages should be written out * - if `verbose` is true, then give extra progress messages * * Returns 0 if all went well, 1 if something went wrong. */ static int play_TS_packets(TS_reader_p tsreader, TS_writer_p tswriter, const tsplay_output_pace_mode pace_mode, uint32_t pid_to_ignore, int max, int loop, int quiet, int verbose) { int err; int total = 0; uint32_t count = 0; int pcrs_used = 0; int pcrs_ignored = 0; uint32_t pcr_pid = ~0U; uint32_t start_count = 0; // which TS packet to loop from offset_t start_posn = 0; if (pace_mode == TSPLAY_OUTPUT_PACE_PCR2_PMT) { // Before we can use PCRs for timing, we need to read a PMT which tells us // what our video stream is (so we can get our PCRs therefrom). err = find_PCR_PID(tsreader,tswriter,&pcr_pid,&start_count,max,quiet); if (err) { fprint_err("### Unable to find PCR PID for timing information\n" " Looked in first %d TS packets\n",max); return 1; } // Once we've found that, we're ready to play our data // If we're looping, remember the location of the first packet of (probable) // data - there's not much point rewinding before that point if (loop) start_posn = start_count * TS_PACKET_SIZE; } count = start_count; for (;;) { byte *data; uint32_t pid; int got_pcr; uint64_t pcr = 0; err = read_TS_packet(tsreader,&count,&data,&pid,&got_pcr,&pcr, max,loop,start_posn,start_count,quiet); if (err == EOF) // shouldn't occur if `loop` break; else if (err) { if (tsreader->file != STDIN_FILENO) { fprint_err("### Last TS packet read was at " LLU_FORMAT "\n", (uint64_t)count * TS_PACKET_SIZE); } return 1; } if (count == start_count + 1) tswrite_discontinuity(tswriter); total ++; // We are only interested in timing information from our PCR PID stream if (got_pcr) { // If 1st PCR we see then remember its pid if (pcr_pid == ~0U) { fprint_msg("PCR PID set to 1st seen: %#x (%d)\n", pid, pid); pcr_pid = pid; } if (pid == pcr_pid) pcrs_used ++; else { if (pcrs_ignored == 0) { fprint_msg("Other PCR PIDs seen: %#x (%d)...\n", pid, pid); } pcrs_ignored ++; got_pcr = FALSE; } } if (pid_to_ignore != 0 && pid == pid_to_ignore) { // We want to "transmit" this packet, since that's the simplest // way of sending its timing information (if any) to the writer. // However, we don't want to *actually* send meaningful data. // The simplest thing is to ignore it if it doesn't have a PCR: // and otherwise, change it to a null packet, by resetting its PID. if (!got_pcr) continue; else { data[2] = 0xFF; data[1] |= 0x1F; } } // And write it out via the circular buffer err = tswrite_write(tswriter,data,pid,got_pcr,pcr); if (err) { fprint_err("### Error writing TS packet %d to circular buffer\n", count); return 1; } if (!quiet && verbose && total%TSPLAY_REPORT_EVERY == 0) fprint_msg("Transferred %d TS packets\n",total); } if (!quiet) { fprint_msg("Transferred %d TS packet%s in total\n",total,(total==1?"":"s")); fprint_msg("Used PCRs from %d packets, ignored PCRs from %d packets\n", pcrs_used,pcrs_ignored); } return 0; } /* * Read TS packets and then output them. * * Assumes (strongly) that it is starting from the start of the file. * * - `input` is the input stream (descriptor) to read * - `tswriter` is our (maybe buffered) writer * - if `pid_to_ignore` is non-zero, then any TS packets with that PID * will not be written out (note: any PCR information in them may still * be used) * - if `scan_for_PCRs`, use a read-ahead buffer to find the *next* PCR, * and thus allow exact timing of packets. * - if we are using the PCR read-ahead buffer, and `override_pcr_pid` is * non-zero, then it is the PID to use for PCRs, ignoring any value found in * a PMT * - if `max` is greater than zero, then at most `max` TS packets should * be read from the input * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) * - if `quiet` is true, then only error messages should be written out * - if `verbose` is true, then give extra progress messages * * Returns 0 if all went well, 1 if something went wrong. */ extern int play_TS_stream(int input, TS_writer_p tswriter, const tsplay_output_pace_mode pace_mode, uint32_t pid_to_ignore, uint32_t override_pcr_pid, int max, int loop, int quiet, int verbose) { int err; TS_reader_p tsreader; err = build_TS_reader(input,&tsreader); if (err) return 1; fprint_msg("pace_mode=%d\n", pace_mode); if (pace_mode == TSPLAY_OUTPUT_PACE_PCR1) err = play_buffered_TS_packets(tsreader,tswriter,pid_to_ignore, override_pcr_pid,max,loop,quiet,verbose); else err = play_TS_packets(tsreader, tswriter, pace_mode, pid_to_ignore, max,loop,quiet,verbose); if (err) { free_TS_reader(&tsreader); return 1; } free_TS_reader(&tsreader); return 0; } /* * Read PS packets and then output them as TS. * * - `input` is the program stream * - `output` is the transport stream * - `pad_start` is the number of filler TS packets to start the output * with. * - `program_repeat` is how often (after how many PS packs) to repeat * the program information (PAT/PMT) * - `want_h264` should be true to indicate that the video stream is H.264 * (ISO/IEC 14496-2, MPEG-4/AVC), false if it is H.262 (ISO/IEC 13818-3, * MPEG-2, or indeed 11172-3, MPEG-1) * - `input_is_dvd` indicates if the PS data came from a DVD, and thus follows * its conventions for private_stream_1 and AC-3/DTS/etc. substreams * - `video_stream` indicates which video stream we want - i.e., the stream * with id 0xE0 + - and -1 means the first video stream found. * - `audio_stream` indicates which audio stream we want. If `want_ac3_audio` * is false, then this will be the stream with id 0xC0 + , or, * if it is -1, the first audio stream found. * - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want * audio from private_stream_1 (0xBD) with substream id , * otherwise we ignore `audio_stream` and assume that all data in * private_stream_1 is the audio we want. * - `want_dolby_as_dvb` indicates if any Dolby (AC-3) audio data should be output * with DVB or ATSC stream type * - `pmt_pid` is the PID of the PMT to write * - `pcr_pid` is the PID of the TS unit containing the PCR * - `video_pid` is the PID for the video we write * - `keep_audio` is true if the audio stream should be output, false if * it should be ignored * - `audio_pid` is the PID for the audio we write * - if `max` is non-zero, then we want to stop reading after we've read * `max` packets * - if `loop`, play the input file repeatedly (up to `max` TS packets * if applicable) * - if `verbose` then we want to output diagnostic information * (nb: only applies to first time if looping is enabled) * - if `quiet` then we want to be as quiet as we can * (nb: only applies to first time if looping is enabled) * * Returns 0 if all went well, 1 if something went wrong. */ extern int play_PS_stream(int input, TS_writer_p output, int pad_start, int program_repeat, int force_stream_type, int want_h262, int input_is_dvd, int video_stream, int audio_stream, int want_ac3_audio, int want_dolby_as_dvb, uint32_t pmt_pid, uint32_t pcr_pid, uint32_t video_pid, int keep_audio, uint32_t audio_pid, int max, int loop, int verbose, int quiet) { int err; int is_h264; PS_reader_p ps; err = build_PS_reader(input,quiet,&ps); if (err) { print_err("### Error building PS reader for input\n"); return 1; } if (force_stream_type) { is_h264 = !want_h262; if (!quiet) fprint_msg("Reading input as %s\n",(want_h262?"MPEG-2 (H.262)": "MPEG-4/AVC (H.264)")); } else { err = determine_if_PS_is_h264(ps,&is_h264); if (err) return 1; if (!quiet) fprint_msg("Video appears to be %s\n", (is_h264?"MPEG-4/AVC (H.264)":"MPEG-2 (H.262)")); } err = ps_to_ts(ps,output,pad_start,program_repeat, is_h264,input_is_dvd, video_stream,audio_stream,want_ac3_audio, want_dolby_as_dvb,pmt_pid,pcr_pid, video_pid,keep_audio,audio_pid,max,verbose,quiet); if (err) { if (loop) print_err("!!! Ignoring error and looping\n"); else { free_PS_reader(&ps); return 1; } } if (loop) { for (;;) { if (!quiet) print_msg("Rewinding and continuing\n"); err = rewind_program_stream(ps); if (err) { print_err("### Error rewinding\n"); free_PS_reader(&ps); return 1; } err = ps_to_ts(ps,output,pad_start,program_repeat, is_h264,input_is_dvd, video_stream,audio_stream,want_ac3_audio, want_dolby_as_dvb,pmt_pid,pcr_pid, video_pid,keep_audio,audio_pid,max,FALSE,TRUE); if (err) { if (loop) print_err("!!! Ignoring error and looping\n"); else { free_PS_reader(&ps); return 1; } } } } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tsreport.c000066400000000000000000001414641261471605300167300ustar00rootroot00000000000000/* * Report on an H.222 transport stream (TS) file. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ts_fns.h" #include "pes_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "pidint_fns.h" #include "fmtx.h" #include "version.h" #define AV_COUNT 2 // Used to mean "PID unset" for continuity_counter monitoring #define INVALID_PID 0x2000 static int tfmt_diff = FMTX_TS_DISPLAY_90kHz_RAW; static int tfmt_abs = FMTX_TS_DISPLAY_90kHz_RAW; static uint64_t estimate_pcr(offset_t posn, uint64_t ppcr_pos, uint64_t ppcr_val, double pcr_rate) { return (uint64_t)(ppcr_val + (27000000.0 * (double)(posn - ppcr_pos))/pcr_rate); } /* ============================================================================ * Buffering reporting */ struct diff_from_pcr { int64_t min; // minimum (absolute) difference uint64_t min_at; // at what PTS the minimum occurred offset_t min_posn; // at what position in the file int64_t max; // and ditto for the maximum (abs) difference uint64_t max_at; offset_t max_posn; int64_t sum; // the sum of all of the differences unsigned int num; // the number of TS records compared }; typedef struct avg_rate_elss { uint64_t time; uint64_t bytes; } avg_rate_el_t; typedef struct avg_ratess { unsigned int max_els; unsigned int in_el; unsigned int out_el; avg_rate_el_t * els; uint64_t max_rate; } avg_rate_t; struct stream_data { uint32_t pid; int stream_type; int had_a_pts; int had_a_dts; int first_cc; int last_cc; int cc_dup_count; int discontinuity_flag_count; uint64_t first_pts; uint64_t first_dts; // Keep these in our datastructure so we can easily report the last // PTS/DTS in the file, when we're finishing up uint64_t pts; uint64_t dts; uint64_t pcr; int err_pts_lt_dts; int err_dts_lt_prev_dts; int err_dts_lt_pcr; int err_cc_error; int err_cc_dup_error; int err_cc_contents; int cc_good; struct diff_from_pcr pcr_pts_diff; struct diff_from_pcr pcr_dts_diff; // Inter DTS max/min values long dts_dts_min; long dts_dts_max; int pts_ne_dts; int pcr_seen; uint64_t first_pcr; uint64_t ts_bytes; avg_rate_t rate; uint8_t last_pkt[188]; }; static int pid_index(struct stream_data *data, int num_streams, uint32_t pid) { int ii; for (ii=0; ii= ar->max_els ? 0 : n + 1; } static void avg_rate_add(avg_rate_t * ar, uint64_t time, uint64_t bytes) { uint64_t gap = 27000000 / 2; // 0.5 sec uint64_t delta_b; uint64_t delta_t; uint64_t rate; if (ar->els == NULL) { ar->max_els = 1024; ar->els = calloc(ar->max_els, sizeof(ar->els[0])); } while (ar->in_el != ar->out_el && pcr_unsigned_diff(time, ar->els[ar->out_el].time) > gap) { ar->out_el = avg_rate_inc(ar, ar->out_el); } ar->els[ar->in_el].time = time; ar->els[ar->in_el].bytes = bytes; delta_b = bytes - ar->els[ar->out_el].bytes; delta_t = pcr_unsigned_diff(time, ar->els[ar->out_el].time); if (delta_t != 0) { rate = (delta_b * 8LL * 27000000LL) / delta_t; if (rate > ar->max_rate) { ar->max_rate = rate; } } ar->in_el = avg_rate_inc(ar, ar->in_el); } /* * Report on the given file * * Returns 0 if all went well, 1 if something went wrong. */ static int report_buffering_stats(TS_reader_p tsreader, const int req_prog_no, int max, int verbose, int quiet, char *output_name, uint32_t continuity_cnt_pid, uint64_t report_mask) { pmt_p pmt = NULL; int err; FILE *file = NULL; int num_streams = 0; FILE *file_cnt = NULL; // Define an arbitrary maximum number of streams we support // -- this is simpler than coping with changing the array sizes // when we find PMTs that indicate we need more streams -- this // limit should be big enough (we believe!) for any file we're // likely to find. #define MAX_NUM_STREAMS 100 struct stream_data stats[MAX_NUM_STREAMS]; // We want to be able to report on how well a simple linear-prediction // model for PCRs would work (i.e., given the last two PCRs, how well // can we predict the *actual* PCR value). It's useful to bundle the // data for that in one place... struct linear_prediction_data { int had_a_pcr; // Have we had a PCR from a PCR PID TS? uint64_t prev_pcr; // if we have, what the last one was offset_t prev_pcr_posn; // and which TS it was from double pcr_rate; int know_pcr_rate; int64_t min_pcr_error; // 27MHz int64_t max_pcr_error; // 27MHz }; struct linear_prediction_data predict = {0}; uint32_t pcr_pid; uint64_t first_pcr = 0; offset_t first_pcr_posn = 0; int pmt_at = 0; // in case we don't look for a PMT int index; int ii; unsigned int pcr_count = 0; uint64_t max_pcr_gap = 0; unsigned int bad_pcr_gap_count = 0; int first = TRUE; offset_t posn = 0; offset_t start_posn = 0; uint32_t count = 0; uint32_t start_count = 0; memset(stats,0,sizeof(stats)); for (ii=0; iiPCR_pid; // Tell the buffering mechanism we want to use it err = prime_read_buffered_TS_packet(tsreader,pcr_pid); if (err) return 1; for (ii=0; iinum_streams; ii++) { uint32_t pid = pmt->streams[ii].elementary_PID; if (ii >= MAX_NUM_STREAMS) { fprint_msg("!!! Found more than %d streams -- just reporting on the first %d found\n", MAX_NUM_STREAMS,MAX_NUM_STREAMS); break; } if (pid >= 0x10 && pid <= 0x1FFE) { stats[num_streams].stream_type = pmt->streams[ii].stream_type; stats[num_streams].pid = pid; num_streams ++; } } fprint_msg("Looking at PCR PID %04x (%d)\n",pcr_pid,pcr_pid); for (ii=0; iiposn - TS_PACKET_SIZE; if (continuity_cnt_pid != INVALID_PID) { file_cnt = fopen("continuity_counter.txt","w"); if (file_cnt == NULL) { print_err("### tsreport: Unable to open file continuity_counter.txt\n"); return 1; } } for (;;) { uint32_t pid; int payload_unit_start_indicator; byte *packet; byte *adapt, *payload; int adapt_len, payload_len; int got_pcr = FALSE; uint64_t acc_pcr = 0; // The accurate PCR per TS packet if (max > 0 && count >= (uint32_t)max) { fprint_msg("Stopping after %d packets (PMT was at %d)\n",max,pmt_at); break; } // Read the next TS packet, taking advantage of our read-ahead buffering // so that we know what its PCR *really* is if (first) { err = read_first_TS_packet_from_buffer(tsreader,pcr_pid,start_count, &packet,&pid,&acc_pcr,&count); posn = start_posn + (count-start_count)*TS_PACKET_SIZE; first = FALSE; } else { err = read_next_TS_packet_from_buffer(tsreader,&packet,&pid,&acc_pcr); count ++; posn += TS_PACKET_SIZE; } if (err == EOF) break; else if (err) { fprint_err("### Error reading TS packet %d at " OFFSET_T_FORMAT "\n",count,posn); return 1; } err = split_TS_packet(packet,&pid, &payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err) { fprint_err("### Error splitting TS packet %d at " OFFSET_T_FORMAT "\n",count,posn); return 1; } // ======================================================================== // If we actually had a PCR, then we need to remember it if (pid == pcr_pid) { uint64_t adapt_pcr; // Do I need to check that this is the same PCR I got earlier? // I certainly hope not... get_PCR_from_adaptation_field(adapt,adapt_len,&got_pcr,&adapt_pcr); if (got_pcr) { ++pcr_count; if (predict.know_pcr_rate) { // OK, so what we have predicted this PCR would be, // given the previous two PCRs and a linear rate? uint64_t guess_pcr = estimate_pcr(posn,predict.prev_pcr_posn, predict.prev_pcr,predict.pcr_rate); int64_t delta = pcr_signed_diff(adapt_pcr, guess_pcr); if (delta < predict.min_pcr_error) predict.min_pcr_error = delta; if (delta > predict.max_pcr_error) predict.max_pcr_error = delta; } if (verbose) fprint_msg(OFFSET_T_FORMAT_8 ": read PCR %s\n", posn, fmtx_timestamp(adapt_pcr, tfmt_abs | FMTX_TS_N_27MHz)); if (file) fprintf(file,OFFSET_T_FORMAT ",read," LLU_FORMAT ",,,,\n", posn,(adapt_pcr / (uint64_t)300) & report_mask); if (predict.had_a_pcr) { if (pcr_signed_diff(predict.prev_pcr, adapt_pcr) > 0) { fprint_err("!!! PCR %s at TS packet " OFFSET_T_FORMAT " is not more than previous PCR %s\n", fmtx_timestamp(adapt_pcr, tfmt_abs | FMTX_TS_N_27MHz), posn, fmtx_timestamp(predict.prev_pcr, tfmt_abs | FMTX_TS_N_27MHz)); } else { uint64_t delta_pcr = pcr_unsigned_diff(adapt_pcr, predict.prev_pcr); int delta_bytes = (int)(posn - predict.prev_pcr_posn); predict.pcr_rate = ((double)delta_bytes * 27.0 / (double)delta_pcr) * 1000000.0; predict.know_pcr_rate = TRUE; if (delta_pcr > max_pcr_gap) max_pcr_gap = delta_pcr; if (delta_pcr > 27000000 / 10) { if (bad_pcr_gap_count++ == 0) fprint_err("!!! PCR gap of %s @ PCR %s > 0.1sec...\n", fmtx_timestamp(delta_pcr, tfmt_diff | FMTX_TS_N_27MHz), fmtx_timestamp(adapt_pcr, tfmt_abs | FMTX_TS_N_27MHz)); } #if 0 // XXX fprint_msg("PCR RATE = %f, DELTA_BYTES = %d, DELTA_PCR " LLU_FORMAT ", PCR = " LLU_FORMAT "\n", predict.pcr_rate,delta_bytes,delta_pcr,adapt_pcr); #endif } } else { if (!quiet) fprint_msg("First PCR at " OFFSET_T_FORMAT "\n",posn); first_pcr = adapt_pcr; first_pcr_posn = posn; predict.had_a_pcr = TRUE; } predict.prev_pcr = adapt_pcr; predict.prev_pcr_posn = posn; } { int i; for (i = 0; i != num_streams; ++i) { stats[i].pcr_seen = TRUE; } } } // end of working with a PCR PID packet // ======================================================================== index = pid_index(stats,num_streams,pid); if (index != -1) { // Do continuity counter checking const int cc = packet[3] & 15; const int is_discontinuity = (adapt != NULL && (adapt[0] & 0x80) != 0); struct stream_data * const ss = stats + index; // Log if required if (continuity_cnt_pid == pid) fprintf(file_cnt, "%d%c", cc, cc == 15 ? '\n' : ' '); // Count flagged discontinuities & note what the first CC in the file is ss->discontinuity_flag_count += is_discontinuity; if (ss->first_cc < 0) ss->first_cc = cc; // CC is meant to increment if we have a payload and not if we don't // CC may legitimately 'be wrong' if the discontinuity flag is set if (ss->last_cc > 0 && !is_discontinuity) { // We are allowed 1 dup packet if (ss->last_cc == cc) { if (payload) { if (ss->cc_dup_count++ != 0) { if (continuity_cnt_pid == pid) fprintf(file_cnt, "[Duplicate error] "); if (ss->err_cc_dup_error++ == 0) { fprint_msg("### PID(%d): Continuity Counter >1 duplicate %d at " OFFSET_T_FORMAT "\n", ss->pid, cc, posn); } } // Whilst everything else must be identical PCR is expected to // change if it is given. If it exists we know where it is. if (!got_pcr ? (memcmp(ss->last_pkt, packet, 188) != 0) : (memcmp(ss->last_pkt, packet, 6) != 0 || memcmp(ss->last_pkt + 12, packet + 12, 188 - 12) != 0)) { if (ss->err_cc_contents++ == 0) fprint_msg("### PID(%d): Continuity Counter duplicate %d: non identical contents at " OFFSET_T_FORMAT "\n", ss->pid, cc, posn); // Assume that non-identical CC means we had a discontinuity and // therefore let this packet through #if 0 { const uint8_t * a = ss->last_pkt; const uint8_t * b = packet; int i; fprint_msg("CC contents:\n"); for (i = 0; i < 188; i += 16, a += 16, b += 16) { int j; const int n = min(16, 188 - i); for (j = 0; j < n; ++j) { fprint_msg("%c%02x", a[j] == b[j] ? ' ' : '*', a[j]); } fprint_msg("\n"); for (j = 0; j < n; ++j) { fprint_msg("%c%02x", a[j] == b[j] ? ' ' : '*', b[j]); } fprint_msg("\n\n"); } } #endif } else { // Real redundant TS packet! // Log it and discard ++ss->cc_good; continue; } } } else { // Otherwise CC must go up by 1 mod 16 ss->cc_dup_count = 0; if (payload) { if (((ss->last_cc + 1) & 15) != cc) { if (continuity_cnt_pid == pid) fprintf(file_cnt, "[Discontinuity] "); if (ss->err_cc_error++ == 0) { fprint_msg("### PID(%d): Continuity Counter discontinuity %d->%d at " OFFSET_T_FORMAT "\n", ss->pid, ss->last_cc, cc, posn); } } } else { // CC not the same but it should be if (continuity_cnt_pid == pid) fprintf(file_cnt, "[Discontinuity] "); if (ss->err_cc_error++ == 0) { fprint_msg("### PID(%d): Continuity Counter discontinuity %d->%d (but no payload) at " OFFSET_T_FORMAT "\n", ss->pid, ss->last_cc, cc, posn); } } } } ss->last_cc = cc; memcpy(ss->last_pkt, packet, 188); } if (index != -1) { if (stats[index].pcr_seen) { stats[index].pcr_seen = FALSE; avg_rate_add(&stats[index].rate, acc_pcr, stats[index].ts_bytes); } if (stats[index].first_pcr == ~(uint64_t)0) stats[index].first_pcr = acc_pcr; stats[index].pcr = acc_pcr; stats[index].ts_bytes += 188; } if (index != -1 && payload && payload_unit_start_indicator) { // We are the start of a PES packet // We'll assume "enough" of the PES packet is in this TS int got_pts, got_dts; const uint64_t last_dts = stats[index].dts; uint64_t pcr_time_now_div300 = 0; int64_t difference; err = find_PTS_DTS_in_PES(payload,payload_len, &got_pts,&stats[index].pts,&got_dts,&stats[index].dts); if (err) { fprint_err("### PID(%d): Error looking for PTS/DTS in TS packet at " OFFSET_T_FORMAT "\n", stats[index].pid, posn); continue; } if (got_dts && !got_pts) { fprint_err("### Got DTS but not PTS, in TS packet at " OFFSET_T_FORMAT "\n",posn); return 1; } if (!got_pts) continue; pcr_time_now_div300 = acc_pcr/300ULL; // Do a few simple checks // For the sake of simplicity we ignore 33bit wrap... if (pts_signed_diff(stats[index].pts, stats[index].dts) < 0) { if (stats[index].err_pts_lt_dts++ == 0) fprint_msg("### PID(%d): PTS (%s) < DTS (%s)\n", stats[index].pid, fmtx_timestamp(stats[index].pts, tfmt_abs), fmtx_timestamp(stats[index].dts, tfmt_abs)); } if (stats[index].had_a_dts) { int64_t dts_dts_diff = pts_signed_diff(stats[index].dts, last_dts); if (dts_dts_diff < stats[index].dts_dts_min) stats[index].dts_dts_min = (long)dts_dts_diff; if (dts_dts_diff > stats[index].dts_dts_max) stats[index].dts_dts_max = (long)dts_dts_diff; if (dts_dts_diff < 0) { if (stats[index].err_dts_lt_prev_dts++ == 0) fprint_msg("### PID(%d): DTS (%s) < previous DTS (%s)\n", stats[index].pid, fmtx_timestamp(stats[index].dts, tfmt_abs), fmtx_timestamp(last_dts, tfmt_abs)); } } if (pts_signed_diff(stats[index].dts, pcr_time_now_div300) < 0) { if (stats[index].err_dts_lt_pcr++ == 0) fprint_msg("### PID(%d): DTS (%s) < PCR (%s)\n", stats[index].pid, fmtx_timestamp(stats[index].dts, tfmt_abs), fmtx_timestamp(acc_pcr, tfmt_abs | FMTX_TS_N_27MHz)); } if (!stats[index].had_a_pts) { #if 0 // XXX Sometimes useful to know fprint_msg(" First stream %d PTS (after first PCR) at " OFFSET_T_FORMAT "\n", index,posn); #endif stats[index].first_pts = stats[index].pts; stats[index].had_a_pts = TRUE; } if (got_dts && !stats[index].had_a_dts) { #if 0 // XXX Sometimes useful to know fprint_msg(" First stream %d DTS (after first PCR) at " OFFSET_T_FORMAT "\n", index,posn); #endif stats[index].first_dts = stats[index].dts; stats[index].had_a_dts = TRUE; } if (got_pts != got_dts || (got_pts && stats[index].pts != stats[index].dts)) stats[index].pts_ne_dts = TRUE; if (file) { // At the moment, we only report any ESCR to the file int got_escr = FALSE; uint64_t escr; (void) find_ESCR_in_PES(payload,payload_len,&got_escr,&escr); fprintf(file,OFFSET_T_FORMAT ",%s," LLU_FORMAT ",%d,%s,", posn, (pcr_pid == pid && got_pcr)?"read":"calc", pcr_time_now_div300 & report_mask, index, IS_AUDIO_STREAM_TYPE(stats[index].stream_type)?"audio": IS_VIDEO_STREAM_TYPE(stats[index].stream_type)?"video":""); fprintf(file,LLU_FORMAT ",",stats[index].pts & report_mask); if (got_dts) fprintf(file,LLU_FORMAT,stats[index].dts & report_mask); else fprintf(file,LLU_FORMAT,stats[index].pts & report_mask); fprintf(file,","); if (got_escr) { if (!quiet) fprint_msg("Found ESCR " LLU_FORMAT " at " OFFSET_T_FORMAT "\n", escr,posn); fprintf(file,LLU_FORMAT,escr & report_mask); } fprintf(file, ",%u", (payload[4] << 8) | payload[5]); fprintf(file,"\n"); } if (verbose) { fprint_msg(OFFSET_T_FORMAT_8 ": %s PCR " LLU_FORMAT " %d %5s", posn, (pcr_pid == pid && got_pcr)?" ":"calc", pcr_time_now_div300, index, IS_AUDIO_STREAM_TYPE(stats[index].stream_type)?"audio": IS_VIDEO_STREAM_TYPE(stats[index].stream_type)?"video":""); } difference = pts_signed_diff(stats[index].pts, pcr_time_now_div300); if (verbose) { fprint_msg(" PTS " LLU_FORMAT,stats[index].pts); print_msg(" PTS-PCR "); fprint_msg(LLD_FORMAT, difference); } if (difference > stats[index].pcr_pts_diff.max) { stats[index].pcr_pts_diff.max = difference; stats[index].pcr_pts_diff.max_at = stats[index].pts; stats[index].pcr_pts_diff.max_posn = posn; } if (difference < stats[index].pcr_pts_diff.min) { stats[index].pcr_pts_diff.min = difference; stats[index].pcr_pts_diff.min_at = stats[index].pts; stats[index].pcr_pts_diff.min_posn = posn; } stats[index].pcr_pts_diff.sum += difference; stats[index].pcr_pts_diff.num ++; if (got_dts) { difference = pts_signed_diff(stats[index].dts, pcr_time_now_div300); if (verbose) { fprint_msg(" DTS " LLU_FORMAT,stats[index].dts); print_msg(" DTS-PCR "); fprint_msg(LLD_FORMAT, difference & report_mask); } if (difference > stats[index].pcr_dts_diff.max) { stats[index].pcr_dts_diff.max = difference; stats[index].pcr_dts_diff.max_at = stats[index].dts; stats[index].pcr_dts_diff.max_posn = posn; } if (difference < stats[index].pcr_dts_diff.min) { stats[index].pcr_dts_diff.min = difference; stats[index].pcr_dts_diff.min_at = stats[index].dts; stats[index].pcr_dts_diff.min_posn = posn; } stats[index].pcr_dts_diff.sum += difference; stats[index].pcr_dts_diff.num ++; } if (verbose) print_msg("\n"); } } if (continuity_cnt_pid != INVALID_PID) { fprintf(file_cnt, "\n"); fclose(file_cnt); } if (!quiet) fprint_msg("Last PCR at " OFFSET_T_FORMAT "\n",predict.prev_pcr_posn); fprint_msg("Read %d TS packet%s\n",count,(count==1?"":"s")); if (pmt) free_pmt(&pmt); if (file) fclose(file); if (predict.had_a_pcr && predict.prev_pcr_posn > first_pcr_posn) { // Multiply by 8 at the end to give us a bit more headroom in file size int rate = (int)((predict.prev_pcr_posn - first_pcr_posn) * 27000000LL / pcr_unsigned_diff(predict.prev_pcr, first_pcr)) * 8; fprint_msg("Overall stream rate=%d bits/sec\n", rate); } fprint_msg("PCRs found: %u, Bad (>.1s) gaps: %u, Max gap: %s\n", pcr_count, bad_pcr_gap_count, fmtx_timestamp(max_pcr_gap, tfmt_diff | FMTX_TS_N_27MHz)); fprint_msg("Linear PCR prediction errors: min=%s, max=%s\n", fmtx_timestamp(predict.min_pcr_error, tfmt_diff | FMTX_TS_N_27MHz), fmtx_timestamp(predict.max_pcr_error, tfmt_diff | FMTX_TS_N_27MHz)); for (ii = 0; ii < num_streams; ii++) { struct stream_data * const ss = stats + ii; fprint_msg("\nStream %d: PID %04x (%d), %s\n",ii,ss->pid,ss->pid, h222_stream_type_str(ss->stream_type)); if (ss->pcr_pts_diff.num > 0) { fprint_msg(" PCR/%s:\n Minimum difference was %6s at DTS %8s, TS packet at " OFFSET_T_FORMAT_8 "\n", ss->pts_ne_dts ? "PTS" : "PTS,DTS", fmtx_timestamp(ss->pcr_pts_diff.min, tfmt_diff), fmtx_timestamp(ss->pcr_pts_diff.min_at, tfmt_abs), ss->pcr_pts_diff.min_posn); fprint_msg(" Maximum difference was %6s at DTS %8s, TS packet at " OFFSET_T_FORMAT_8 "\n", fmtx_timestamp(ss->pcr_pts_diff.max, tfmt_diff), fmtx_timestamp(ss->pcr_pts_diff.max_at, tfmt_abs), ss->pcr_pts_diff.max_posn); fprint_msg(" i.e., a span of %s\n", fmtx_timestamp(ss->pcr_pts_diff.max - ss->pcr_pts_diff.min, tfmt_diff)); fprint_msg(" Mean difference (of %u) is %s\n", ss->pcr_pts_diff.num, fmtx_timestamp((int64_t)(ss->pcr_pts_diff.sum/(double)ss->pcr_pts_diff.num), tfmt_diff)); } if (ss->pcr_dts_diff.num > 0 && ss->pts_ne_dts) { fprint_msg(" PCR/DTS:\n Minimum difference was %6s at DTS %8s, TS packet at " OFFSET_T_FORMAT_8 "\n", fmtx_timestamp(ss->pcr_dts_diff.min, tfmt_diff), fmtx_timestamp(ss->pcr_dts_diff.min_at, tfmt_abs), ss->pcr_dts_diff.min_posn); fprint_msg(" Maximum difference was %6s at DTS %8s, TS packet at " OFFSET_T_FORMAT_8 "\n", fmtx_timestamp(ss->pcr_dts_diff.max, tfmt_diff), fmtx_timestamp(ss->pcr_dts_diff.max_at, tfmt_abs), ss->pcr_dts_diff.max_posn); fprint_msg(" i.e., a span of %s\n", fmtx_timestamp(ss->pcr_dts_diff.max - ss->pcr_dts_diff.min, tfmt_diff)); fprint_msg(" Mean difference (of %u) is %s\n", ss->pcr_dts_diff.num, fmtx_timestamp((int64_t)(ss->pcr_dts_diff.sum/(double)ss->pcr_dts_diff.num), tfmt_diff)); } if (ss->had_a_dts) { fprint_msg(" DTS-last DTS: min=%s, max=%s\n", fmtx_timestamp(ss->dts_dts_min, tfmt_diff), fmtx_timestamp(ss->dts_dts_max, tfmt_diff)); } fprint_msg(" First PCR %8s, last %8s\n", fmtx_timestamp(first_pcr, tfmt_abs | FMTX_TS_N_27MHz), fmtx_timestamp(predict.prev_pcr, tfmt_abs | FMTX_TS_N_27MHz)); if (ss->pcr_pts_diff.num > 0) fprint_msg(" First PTS %8s, last %8s\n", fmtx_timestamp(ss->first_pts, tfmt_abs), fmtx_timestamp(ss->pts, tfmt_abs)); if (ss->pcr_dts_diff.num > 0) fprint_msg(" First DTS %8s, last %8s\n", fmtx_timestamp(ss->first_dts, tfmt_abs), fmtx_timestamp(ss->dts, tfmt_abs)); { // Calculate rate over the range of PCRs seen in this stream uint64_t avg = ss->pcr == ss->first_pcr ? 0LL : ((ss->ts_bytes - 188LL) * 8LL * 27000000LL) / pcr_unsigned_diff(ss->pcr, ss->first_pcr); fprint_msg(" Stream: %llu bytes; rate: avg %llu bits/s, max %llu bits/s\n", ss->ts_bytes, avg, ss->rate.max_rate); } if (ss->discontinuity_flag_count != 0) fprint_msg(" Discontinuity flags: *%d", ss->discontinuity_flag_count); fprint_msg(" CC: first: %d, last: %d; duplicate packets: %d\n", ss->first_cc, ss->last_cc, ss->cc_good); if (ss->err_cc_error != 0) fprint_msg(" ### CC error * %d\n", ss->err_cc_error); if (ss->err_cc_contents != 0) fprint_msg(" ### CC contents error * %d\n", ss->err_cc_contents); if (ss->err_cc_dup_error != 0) fprint_msg(" ### CC duplicate error * %d\n", ss->err_cc_dup_error); if (ss->err_pts_lt_dts != 0) fprint_msg(" ### PTS < DTS * %d\n", ss->err_pts_lt_dts); if (ss->err_dts_lt_prev_dts != 0) fprint_msg(" ### DTS < prev DTS * %d\n", ss->err_dts_lt_prev_dts); if (ss->err_dts_lt_pcr != 0) fprint_msg(" ### DTS < PCR * %d\n", ss->err_dts_lt_pcr); } return 0; } /* * Report on the given file * * Returns 0 if all went well, 1 if something went wrong. */ static int report_ts(TS_reader_p tsreader, int max, int verbose, int show_data, int report_timing) { struct timing times = {0}; pidint_list_p prog_list = NULL; pmt_p pmt = NULL; int err; int count = 0; timing_p time_ptr = NULL; byte *pat_data = NULL; int pat_data_len = 0; int pat_data_used = 0; uint32_t unfinished_pmt_pid = 0; byte *pmt_data = NULL; int pmt_data_len = 0; int pmt_data_used = 0; if (report_timing) time_ptr = × for (;;) { uint32_t pid; int payload_unit_start_indicator; byte *adapt, *payload; int adapt_len, payload_len; if (max > 0 && count >= max) { fprint_msg("Stopping after %d packets\n",max); break; } err = get_next_TS_packet(tsreader,&pid, &payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err == EOF) break; else if (err) { fprint_err("### Error reading TS packet %d at " OFFSET_T_FORMAT "\n",count,tsreader->posn - TS_PACKET_SIZE); free_pidint_list(&prog_list); if (pmt_data) free(pmt_data); return 1; } count ++; if (verbose) fprint_msg(OFFSET_T_FORMAT_8 ": TS Packet %2d PID %04x%s", tsreader->posn - TS_PACKET_SIZE,count,pid, (payload_unit_start_indicator?" [pusi]":"")); // Report on what we may if (verbose) { if (pid == 0x1fff) print_msg(" PADDING - ignored\n"); else if (pid == 0x0000) print_msg(" PAT\n"); else if (pid == 0x0001) print_msg(" Conditional Access Table - ignored\n"); else if (pid >= 0x0002 && pid <= 0x000F) print_msg(" RESERVED - ignored\n"); else if (pid_in_pidint_list(prog_list,pid)) print_msg(" PMT\n"); else if (pid_in_pmt(pmt,pid)) { pmt_stream_p stream = pid_stream_in_pmt(pmt,pid); if (stream == NULL) { fprint_err("### Internal error: stream for PID %0x returned NULL" " in PMT\n",pid); report_pmt(FALSE," ",pmt); free_pidint_list(&prog_list); free_pmt(&pmt); if (pmt_data) free(pmt_data); return 1; } fprint_msg(" stream type %02x (%s)\n", stream->stream_type,h222_stream_type_str(stream->stream_type)); } else print_msg(" stream type not identified\n"); } // Ignore padding packets if (pid == 0x1fff) continue; // Conditional Access Tables *might* contain a PCR - do we want // to ignore them anyway? Well, since I've never seen one, do so for now if (pid == 0x0001) continue; if (report_timing) report_adaptation_timing(time_ptr,adapt,adapt_len,count); else if (verbose) report_adaptation_field(adapt,adapt_len); if (pid == 0) { if (payload_unit_start_indicator && pat_data) { // Lose any data we started but didn't complete free(pat_data); pat_data = NULL; pat_data_len = 0; pat_data_used = 0; } else if (!payload_unit_start_indicator && !pat_data) { fprint_err("!!! Discarding partial (unstarted) PAT in TS" " packet at " OFFSET_T_FORMAT "\n", tsreader->posn - TS_PACKET_SIZE); continue; } err = build_psi_data(verbose,payload,payload_len,pid, &pat_data,&pat_data_len,&pat_data_used); if (err) { fprint_err("### Error %s PAT in TS packet at " OFFSET_T_FORMAT "\n", (payload_unit_start_indicator?"starting new":"continuing"), tsreader->posn - TS_PACKET_SIZE); free_pidint_list(&prog_list); if (pat_data) free(pat_data); return 1; } // Still need more data for this PAT if (pat_data_len > pat_data_used) continue; // Free any earlier program list we'd read, now we've got a new one free_pidint_list(&prog_list); err = extract_prog_list_from_pat(verbose,pat_data,pat_data_len,&prog_list); if (err) { fprint_err("### Error extracting program list from PAT in TS" " packet at " OFFSET_T_FORMAT "\n", tsreader->posn - TS_PACKET_SIZE); free_pidint_list(&prog_list); if (pat_data) free(pat_data); return 1; } if (pat_data) free(pat_data); pat_data = NULL; pat_data_len = 0; pat_data_used = 0; } else if (pid_in_pidint_list(prog_list,pid)) { // We don't cope with interleaved PMT's with different PIDs if (unfinished_pmt_pid != 0 && pid != unfinished_pmt_pid) { // We're already part way through a PMT packet, but it's not // the same PMT as the one in this TS packet if (payload_unit_start_indicator) { // This is the start (and maybe also the end) of a new PMT, // so let's read this one // - actually, we don't need to do anything here, as our // data will get "thrown away" further down } else { // This is the continuation of another PMT - let's ignore // it for now and hope we'll find the rest of the one we're // still waiting to finish fprint_err("!!! Discarding partial PMT with PID %04x in TS" " packet at " OFFSET_T_FORMAT ", already building PMT with PID %04x\n", unfinished_pmt_pid, tsreader->posn - TS_PACKET_SIZE,pid); continue; } } if (payload_unit_start_indicator && pmt_data) { // Lose any data we started but didn't complete free(pmt_data); pmt_data = NULL; pmt_data_len = 0; pmt_data_used = 0; } else if (!payload_unit_start_indicator && !pmt_data) { fprint_err("!!! Discarding partial (unstarted) PMT in TS" " packet at " OFFSET_T_FORMAT "\n", tsreader->posn - TS_PACKET_SIZE); continue; } err = build_psi_data(verbose,payload,payload_len,pid, &pmt_data,&pmt_data_len,&pmt_data_used); if (err) { fprint_err("### Error %s PMT in TS packet at " OFFSET_T_FORMAT "\n", (payload_unit_start_indicator?"starting new":"continuing"), tsreader->posn - TS_PACKET_SIZE); free_pidint_list(&prog_list); free_pmt(&pmt); if (pmt_data) free(pmt_data); return 1; } // Still need more data for this PMT if (pmt_data_len > pmt_data_used) { unfinished_pmt_pid = pid; continue; } // Free any earlier PMT data we'd read, now we've got a new one free_pmt(&pmt); // Which isn't unfinished anymore unfinished_pmt_pid = 0; err = extract_pmt(verbose,pmt_data,pmt_data_len,pid,&pmt); if (err) { fprint_err("### Error extracting stream list from PMT in TS" " packet at " OFFSET_T_FORMAT "\n", tsreader->posn - TS_PACKET_SIZE); free_pidint_list(&prog_list); free_pmt(&pmt); if (pmt_data) free(pmt_data); return err; } if (pmt_data) free(pmt_data); pmt_data = NULL; pmt_data_len = 0; pmt_data_used = 0; #if 0 print_msg("PMT data read as:\n"); report_pmt(TRUE," ",pmt); print_msg("\n"); #endif } else if (verbose) { pmt_stream_p stream = pid_stream_in_pmt(pmt,pid); int stream_type; if (stream == NULL) stream_type = -1; else stream_type = stream->stream_type; report_payload(show_data,stream_type,payload,payload_len, payload_unit_start_indicator); if (!show_data && payload_unit_start_indicator) { print_data(TRUE," Data",payload,payload_len,20); } #if 0 // XXX print_end_of_data(" ",payload,payload_len,20); #endif } } fprint_msg("Read %d TS packet%s\n",count,(count==1?"":"s")); free_pidint_list(&prog_list); free_pmt(&pmt); if (pmt_data) free(pmt_data); return 0; } /* * Report on TS packets with a particular PID in the given file * * Returns 0 if all went well, 1 if something went wrong. */ static int report_single_pid(TS_reader_p tsreader, int max, int quiet, uint32_t just_pid) { int err; int count = 0; int pid_count = 0; for (;;) { uint32_t pid; int payload_unit_start_indicator; byte *adapt, *payload; int adapt_len, payload_len; if (max > 0 && pid_count >= max) { fprint_msg("Stopping after %d packets with PID %0x\n",max,just_pid); break; } err = get_next_TS_packet(tsreader,&pid, &payload_unit_start_indicator, &adapt,&adapt_len,&payload,&payload_len); if (err == EOF) break; else if (err) { fprint_err("### Error reading TS packet %d at " OFFSET_T_FORMAT "\n",count,tsreader->posn - TS_PACKET_SIZE); return 1; } count ++; if (pid != just_pid) continue; pid_count ++; if (!quiet) { fprint_msg(OFFSET_T_FORMAT_8 ": TS Packet %2d PID %04x%s\n", tsreader->posn - TS_PACKET_SIZE,count,pid, (payload_unit_start_indicator?" [pusi]":"")); if (adapt_len > 0) print_data(TRUE," Adapt",adapt,adapt_len,adapt_len); print_data(TRUE, " Payload",payload,payload_len,payload_len); } } fprint_msg("Read %d TS packet%s, %d with PID %0x\n", count,(count==1?"":"s"),pid_count,just_pid); return 0; } static void print_usage() { print_msg( "Usage: tsreport [switches] [] [switches]\n" "\n" ); REPORT_VERSION("tsreport"); print_msg( "\n" " Report on one of the following for the given Transport Stream:\n" "\n" " * The number of TS packets.\n" " * PCR and PTS/DTS differences (-buffering).\n" " * The packets of a single PID (-justpid).\n" "\n" " When conflicting switches are specified, the last takes effect.\n" "\n" "Input:\n" " Read data from the named H.222 Transport Stream file\n" " -stdin Read data from standard input\n" "\n" "Normal operation:\n" " By default, normal operation just reports the number of TS packets.\n" " -timing, -t Report timing information based on the PCRs.\n" " -data Show TS packet/payload data as bytes\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -verbose, -v Also output (fairly detailed) information on each TS packet.\n" " -quiet, -q Only output summary information (this is the default)\n" " -max , -m Maximum number of TS packets to read\n" "\n" "Buffering information:\n" " -buffering, -b Report on the differences between PCR and PTS, and\n" " between PCR and DTS. This is relevant to the size of\n" " buffers needed in the decoder. Also reports bitrates;\n" " the max bitrate is calculated over 0.5sec\n" " -o Output CSV data for -buffering to the named file.\n" " -32 Truncate 33 bit values in the CSV output to 32 bits\n" " (losing the top bit).\n" " -verbose, -v Output PCR/PTS/DTS information as it is found (in a\n" " format similar to that used for -o)\n" " -quiet, -q Output less information (notably, not the PMT)\n" " -cnt , Check values of continuity_counter in the specified PID.\n" " Writes all the values of the counter to a file called\n" " 'continuity_counter.txt'. Turns buffering on (-b).\n" " -max , -m Maximum number of TS packets to read\n" " -prog Report on program [default = 1]\n" " (hopefully default will be 'all' in the future)\n" "\n" "Single PID:\n" " -justpid Just show data (file offset, index, adaptation field\n" " and payload) for TS packets with the given PID.\n" " PID 0 is allowed (i.e., the PAT).\n" " -verbose, -v Is ignored\n" " -quiet, -q Is ignored\n" " -max , -m Maximum number of TS packets of that PID to read\n" "\n" "Experimental control of timestamp formats (this doesn't affect the output\n" "to the CVS file, produced with -o):\n" " -tfmt Specify format of time differences.\n" " -tafmt Specify format of absolute times.\n" "\n" " is (currently, but may change) one of:\n" " 90 Default -- show as 90KHz timestamps (suffix 't' on\n" " the values: e.g., 4362599t).\n" " 27 Show as 27MHz timestamps (similar, e.g., 25151:000t).\n" " 32 Show as 90KHz timestamps, but only the low 32 bits.\n" " ms Show as milliseconds.\n" " hms Show as hours/minutes/seconds (H:MM:SS.ssss, the H\n" " can be more than one digit if necessary)\n" ); } int main(int argc, char **argv) { int use_stdin = FALSE; char *input_name = NULL; int had_input_name = FALSE; TS_reader_p tsreader = NULL; int max = 0; // The maximum number of TS packets to read (or 0) int verbose = FALSE; // True => output diagnostic/progress messages int quiet = FALSE; int report_timing = FALSE; int report_buffering = FALSE; int show_data = FALSE; char *output_name = NULL; uint32_t continuity_cnt_pid = INVALID_PID; int req_prog_no = 1; uint64_t report_mask = ~0; // report as many bits as we get int select_pid = FALSE; uint32_t just_pid = 0; int err = 0; int ii = 1; if (argc < 2) { print_usage(); return 0; } while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-h",argv[ii]) || !strcmp("-help",argv[ii])) { print_usage(); return 0; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-err",argv[ii])) { CHECKARG("tsreport",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### tsreport: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-timing",argv[ii]) || !strcmp("-t",argv[ii])) { report_timing = TRUE; quiet = FALSE; } else if (!strcmp("-buffering",argv[ii]) || !strcmp("-b",argv[ii])) { report_buffering = TRUE; quiet = FALSE; } else if (!strcmp("-o",argv[ii])) { CHECKARG("tsreport",ii); output_name = argv[ii+1]; ii ++; } else if (!strcmp("-cnt",argv[ii])) { CHECKARG("tsreport",ii); err = unsigned_value("tsreport",argv[ii],argv[ii+1],10,&continuity_cnt_pid); if (err) return 1; fprint_msg("Reporting on continuity_counter for pid = %04x (%u)\n", continuity_cnt_pid,continuity_cnt_pid); report_buffering = TRUE; quiet = FALSE; ii ++; } else if (!strcmp("-data",argv[ii])) { show_data = TRUE; quiet = FALSE; } else if (!strcmp("-32",argv[ii])) { report_mask = 0xFFFFFFFF; // i.e., bottom 32 bits only } else if (!strcmp("-tfmt",argv[ii])) { CHECKARG("tsreport",ii); if ((tfmt_diff = fmtx_str_to_timestamp_flags(argv[ii + 1])) < 0) { fprint_msg("### tsreport: Bad timestamp format '%s'\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-tafmt",argv[ii])) { CHECKARG("tsreport",ii); if ((tfmt_abs = fmtx_str_to_timestamp_flags(argv[ii + 1])) < 0) { fprint_msg("### tsreport: Bad timestamp format '%s'\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-justpid",argv[ii])) { CHECKARG("tsreport",ii); err = unsigned_value("tsreport",argv[ii],argv[ii+1],0,&just_pid); if (err) return 1; select_pid = TRUE; ii++; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("tsreport",ii); err = int_value("tsreport",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-stdin",argv[ii])) { use_stdin = TRUE; had_input_name = TRUE; // so to speak } else if (!strcmp("-prog",argv[ii])) { CHECKARG("tsreport",ii); err = int_value("tsreport",argv[ii],argv[ii+1],TRUE,10,&req_prog_no); if (err) return 1; ii++; } else { fprint_err("### tsreport: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name) { fprint_err("### tsreport: Unexpected '%s'\n",argv[ii]); return 1; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### tsreport: No input file specified\n"); return 1; } err = open_file_for_TS_read((use_stdin?NULL:input_name),&tsreader); if (err) { fprint_err("### tsreport: Unable to open input file %s for reading TS\n", use_stdin?"":input_name); return 1; } fprint_msg("Reading from %s\n",(use_stdin?"":input_name)); if (max) fprint_msg("Stopping after %d TS packets\n",max); if (select_pid) err = report_single_pid(tsreader,max,quiet,just_pid); else if (report_buffering) err = report_buffering_stats(tsreader,req_prog_no,max,verbose,quiet, output_name,continuity_cnt_pid,report_mask); else err = report_ts(tsreader,max,verbose,show_data,report_timing); if (err) { print_err("### tsreport: Error reporting on input stream\n"); (void) close_TS_reader(&tsreader); return 1; } err = close_TS_reader(&tsreader); if (err) return 1; return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tsserve.c000066400000000000000000003750551261471605300165460ustar00rootroot00000000000000/* * Serve TS packets from TS or PS data, supporting playing forwards * at normal and accelerated speeds, and reverse play. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #include #else // _WIN32 #include #include #include #include // WNOHANG #include // sockaddr_in #include // sigaction, etc. #endif // _WIN32 #include // Sleeping and timing #include "compat.h" #include "ts_fns.h" #include "ps_fns.h" #include "pes_fns.h" #include "accessunit_fns.h" #include "nalunit_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "tswrite_fns.h" #include "es_fns.h" #include "h262_fns.h" #include "filter_fns.h" #include "reverse_fns.h" #include "version.h" //#define DEBUG #define SHOW_REVERSE_DATA 1 #define DEBUG_COMMANDS 1 #define TIME_SKIPPING 1 #if TIME_SKIPPING #include #endif #define DEFAULT_REVERSE_FREQUENCY 8 #define DEFAULT_FORWARD_FREQUENCY 8 #define FRAMES_FOR_ONE_SECOND 25 #define SMALL_SKIP_DISTANCE 10*FRAMES_FOR_ONE_SECOND // 10 seconds #define BIG_SKIP_DISTANCE 3*60*FRAMES_FOR_ONE_SECOND // 3 minutes static int extra_info = 0; // What we are to do enum ACTION { ACTION_SERVER, // The default action is to be a server ACTION_CMD, // An alternative is to connect and read commands ACTION_TEST, // One of the testing modes }; #define MAX_INPUT_FILES 10 // i.e., 0..9 // Command line data // There's a lot of data from the command line that needs passing down // from the top level to the main processing functions, so let's package // it up neatly struct tsserve_context { char *input_names[MAX_INPUT_FILES]; // The files to read from int default_file_index; // Which one is the default int video_only; // As it says - no audio? int pad_start; // Number of filler packets to output at start int ffrequency; // Fast forward frequency when filtering int rfrequency; // Base reverse frequency int with_seq_hdrs;// For H.262, output sequence headers when not // doing normal play? int pes_padding; // Number of dummy PES packets to output per real packet int drop_packets; // 0 or drop TS packets every on output int drop_number; // how many packets to drop // Program Stream specific options uint32_t pmt_pid; uint32_t audio_pid; uint32_t video_pid; uint32_t pcr_pid; int want_h262; int dolby_is_dvb; int force_stream_type; int repeat_program_every; // Transport Stream specific options int tsdirect; }; typedef struct tsserve_context *tsserve_context_p; // ============================================================ // Unions to give us a single view of the two forms of data stream // ============================================================ // The form of this single view is limited solely to what is needed // in this program - it is not intended to be a general unification // of the two types of data. // Accessing the data stream union u_stream_context { h262_context_p h262; access_unit_context_p h264; }; struct _stream_context { int is_h262; int program_number; union u_stream_context u; }; typedef struct _stream_context stream_context; typedef struct _stream_context *stream_context_p; // Filtering union u_filter_context { h262_filter_context_p h262; h264_filter_context_p h264; }; struct _filter_context { int is_h262; union u_filter_context u; }; typedef struct _filter_context filter_context; typedef struct _filter_context *filter_context_p; // Pictures union u_picture { int is_h262; h262_picture_p h262; access_unit_p h264; }; struct _picture { int is_h262; union u_picture u; int type; // For H.262, the picture coding type. 0xFF means seq hdr }; typedef struct _picture picture; typedef struct _picture *picture_p; // ============================================================ // Utilities to hide the difference between the two data stream types // ============================================================ // A macro to avoid my mistyping this on the several occasions I need it #define EXTRACT_ES_FROM_STREAM(stream) \ ((stream).is_h262?(stream).u.h262->es:(stream).u.h264->nac->es) // A related macro for reverse data #define EXTRACT_REVERSE_FROM_STREAM(stream) \ ((stream).is_h262?(stream).u.h262->reverse_data:(stream).u.h264->reverse_data) /* * Note that `program_number` should be 1 or more. */ static int build_stream(ES_p es, int is_h262, int program_number, stream_context *stream) { int err; stream->is_h262 = is_h262; stream->program_number = program_number; if (is_h262) { err = build_h262_context(es,&(stream->u.h262)); if (err) { print_err("### Error building H.262 context\n"); return 1; } } else { err = build_access_unit_context(es,&(stream->u.h264)); if (err) { print_err("### Error building H.264 access unit context\n"); return 1; } } return 0; } static void close_stream(stream_context stream) { if (stream.is_h262) free_h262_context(&(stream.u.h262)); else free_access_unit_context(&(stream.u.h264)); } static int build_and_attach_reverse(stream_context stream, reverse_data_p *reverse_data) { int err; err = build_reverse_data(reverse_data,!stream.is_h262); if (err) { print_err("### Unable to build reverse memory\n"); return 1; } if (stream.is_h262) add_h262_reverse_context(stream.u.h262,*reverse_data); else add_access_unit_reverse_context(stream.u.h264,*reverse_data); return 0; } static int build_filter_context(stream_context stream, int is_strip, int frequency, filter_context *fcontext) { int err; fcontext->is_h262 = stream.is_h262; if (stream.is_h262) { if (is_strip) err = build_h262_filter_context_strip(&(fcontext->u.h262), stream.u.h262,TRUE); else err = build_h262_filter_context(&(fcontext->u.h262), stream.u.h262,frequency); } else { if (is_strip) err = build_h264_filter_context_strip(&(fcontext->u.h264), stream.u.h264,TRUE); else err = build_h264_filter_context(&(fcontext->u.h264), stream.u.h264,frequency); } return err; } static void free_filter_context(filter_context fcontext) { if (fcontext.is_h262) free_h262_filter_context(&(fcontext.u.h262)); else free_h264_filter_context(&(fcontext.u.h264)); } /* * "Reset" a stream, such that the picture reading contexts do not contain * any past memory. */ static inline void reset_stream(stream_context stream) { if (stream.is_h262) { if (stream.u.h262->last_item) free_h262_item(&stream.u.h262->last_item); } else { reset_access_unit_context(stream.u.h264); } } /* * Retrieve the next picture. Doesn't distinguish H.262 sequence headers * and pictures. */ static inline int get_next_picture(stream_context stream, int verbose, int quiet, picture *pic) { int err; if (stream.is_h262) { h262_picture_p picture; err = get_next_h262_frame(stream.u.h262,verbose,quiet,&picture); if (err) return err; pic->is_h262 = TRUE; pic->u.h262 = picture; if (picture->is_picture) pic->type = picture->picture_coding_type; else pic->type = 0xff; } else { access_unit_p unit; err = get_next_h264_frame(stream.u.h264,quiet,verbose,&unit); if (err) return err; pic->is_h262 = FALSE; pic->u.h264 = unit; } return 0; } static inline void free_picture(picture *pic) { if (pic->is_h262) free_h262_picture(&pic->u.h262); else free_access_unit(&pic->u.h264); pic->type = 0; } /* * Needs to be told if the picture is H.262 or not, because this may be * called on an unused instance of the picture data structure. */ static inline void unset_picture(int is_h262, picture *pic) { if (is_h262) pic->u.h262 = NULL; else pic->u.h264 = NULL; pic->is_h262 = is_h262; } static inline int is_null_picture(picture pic) { if (pic.is_h262) return pic.u.h262 == NULL; else return pic.u.h264 == NULL; } // NB: there is already a macro called "is_seq_header" static inline int is_non_frame(picture pic) { return (pic.is_h262 && pic.type == 0xff); } static inline int is_reference_picture(picture pic) { if (pic.is_h262) return (pic.type == 1 || pic.type == 2); else return (pic.u.h264->primary_start != NULL && pic.u.h264->primary_start->nal_ref_idc != 0); } static inline int is_I_or_IDR_picture(picture pic) { if (pic.is_h262) return (pic.type == 1); else return (pic.u.h264->primary_start != NULL && pic.u.h264->primary_start->nal_ref_idc != 0 && (pic.u.h264->primary_start->nal_unit_type == NAL_IDR || all_slices_I(pic.u.h264))); } static void print_picture(picture pic) { if (pic.is_h262) { if (pic.type == 0xff) print_msg("sequence header"); else fprint_msg("%s picture",H262_PICTURE_CODING_STR(pic.type)); } else { if (pic.u.h264->primary_start == NULL) print_msg(""); else fprint_msg("idc %d/type %d (%s)", pic.u.h264->primary_start->nal_ref_idc, pic.u.h264->primary_start->nal_unit_type, NAL_UNIT_TYPE_STR(pic.u.h264->primary_start->nal_unit_type)); } } static inline int write_picture_as_TS(stream_context stream, TS_writer_p output, picture pic) { ES_p es = EXTRACT_ES_FROM_STREAM(stream); if (stream.is_h262) return write_h262_picture_as_TS(output,pic.u.h262, es->reader->output_video_pid); else return write_access_unit_as_TS(pic.u.h264,stream.u.h264, output,es->reader->output_video_pid); } static inline void reset_filter_context(filter_context fcontext, int frequency) { if (fcontext.is_h262) { reset_h262_filter_context(fcontext.u.h262); fcontext.u.h262->freq = frequency; } else { reset_h264_filter_context(fcontext.u.h264); fcontext.u.h264->freq = frequency; } } static inline int get_next_stripped(filter_context fcontext, int verbose, int quiet, picture *seq_hdr, picture *this_picture, int *delta_pictures_seen) { int err; unset_picture(fcontext.is_h262,seq_hdr); unset_picture(fcontext.is_h262,this_picture); if (fcontext.is_h262) { h262_picture_p _this_picture = NULL; h262_picture_p _seq_hdr = NULL; err = get_next_stripped_h262_frame(fcontext.u.h262,verbose,quiet,&_seq_hdr, &_this_picture,delta_pictures_seen); seq_hdr->u.h262 = _seq_hdr; this_picture->u.h262 = _this_picture; } else { access_unit_p this_unit = NULL; err = get_next_stripped_h264_frame(fcontext.u.h264,verbose,quiet, &this_unit,delta_pictures_seen); this_picture->u.h264 = this_unit; } return err; } static inline int get_next_filtered(filter_context fcontext, int verbose, int quiet, picture *seq_hdr, picture *this_picture, int *delta_pictures_seen) { int err; unset_picture(fcontext.is_h262,seq_hdr); unset_picture(fcontext.is_h262,this_picture); if (fcontext.is_h262) { h262_picture_p _this_picture = NULL; h262_picture_p _seq_hdr = NULL; err = get_next_filtered_h262_frame(fcontext.u.h262,verbose,quiet,&_seq_hdr, &_this_picture,delta_pictures_seen); seq_hdr->u.h262 = _seq_hdr; this_picture->u.h262 = _this_picture; } else { access_unit_p this_unit = NULL; err = get_next_filtered_h264_frame(fcontext.u.h264,verbose,quiet, &this_unit,delta_pictures_seen); this_picture->u.h264 = this_unit; } return err; } // ============================================================ // A common view of handling the two types of data stream // ============================================================ /* * Playing at normal speed happens as a "side effect" of gathering * information to allow us to reverse. Basically, each time a PES packet * is read in, it gets automatically written out for us, whilst we * analyse its contents. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int play_normal(stream_context stream, TS_writer_p output, int verbose, int quiet, int num_normal, int tsdirect, reverse_data_p reverse_data) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; if (extra_info) print_msg("Playing at normal speed\n"); /* Do not write program data if we're in tsdirect mode - * it'll change and some programs can't cope */ if (!tsdirect) { err = write_program_data(reader,output); if (err) return err; } start_server_output(reader); if (stream.is_h262) { err = collect_reverse_h262(stream.u.h262,num_normal,verbose,quiet); if (err) return err; } else { err = collect_reverse_access_units(stream.u.h264,num_normal,verbose,quiet); if (err) return err; } return 0; } /* * Flush our PES packet after normal play * * Call this with server output still on. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int flush_after_normal(stream_context stream, TS_writer_p output, int verbose, int quiet) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; ES_offset item_start; if (extra_info) print_msg("Flushing PES data after normal play\n"); if (reader->packet == NULL) { // We're apparently at the end of file, so there's not much we can do return 0; } // When playing forwards at normal speed, each PES packet is read in, // processed to extract information, and then (automatically) written // out again when the next PES packet is read in. // // However, when we start doing fast forward or reverse, that automatic // output of PES packets is switched off. Thus it is up to us to ensure // that any outstanding data gets output before that. // // A command character is received when a write (of a PES packet) is made, // and such a write is (as said above) triggered when a new PES packet has // to be read in. *That* happens when reading the next ES item requires // reading a byte from the next PES packet. Thus we know that the current // picture started in the last PES packet, and that the ES item that // comes after it ends in the next packet. // // To terminate the data for that picture neatly in the output, we thus // need to output the ES data from this PES packet up to the end of the // current picture. if (stream.is_h262) { if (stream.u.h262->last_item == NULL) { if (extra_info) print_msg(".. no H.262 last item\n"); return 0; // not much else we can do } // The ES item that comes after (and thus marks the end of) the // last picture *starts* at: item_start = stream.u.h262->last_item->unit.start_posn; } else { if (stream.u.h264->pending_nal == NULL) { // We ended the previous access unit for some reason that didn't // need to read the next NAL unit, or we've not read anything in yet item_start = es->posn_of_next_byte; } else { // The previous access unit was ended by this "pending" NAL unit, // so the "next" item presumably starts with that... item_start = stream.u.h264->pending_nal->unit.start_posn; } } if (extra_info) fprint_msg(".. last item starts at " OFFSET_T_FORMAT "/%d\n", item_start.infile,item_start.inpacket); // We know we haven't written out any data for the current PES packet // - do we need to? if (item_start.infile < reader->packet->posn) { // The terminating item started in the previous packet, which we've // already output. We should read in the next picture, and output // that part of it which hasn't already been output. picture picture; if (extra_info) print_msg(".. which is in the previous packet - " "reading spanning picture into next packet\n"); err = get_next_picture(stream,verbose,quiet,&picture); if (err == EOF) { // Clearly there is no next picture if (extra_info) print_msg("End of file\n"); return err; } else if (err) { print_err("### Error trying to read into next packet whilst" " flushing after normal play\n"); return 1; } free_picture(&picture); // We now know that we want to output from the current PES packet to the // end of this picture, which is one byte before the new terminating // item if (stream.is_h262) item_start = stream.u.h262->last_item->unit.start_posn; else { if (stream.u.h264->pending_nal == NULL) item_start = es->posn_of_next_byte; else item_start = stream.u.h264->pending_nal->unit.start_posn; } if (extra_info) fprint_msg(".. new last item starts at " OFFSET_T_FORMAT "/%d\n",item_start.infile,item_start.inpacket); } if (item_start.inpacket == 0) { // The terminating item started at the beginning of this packet, // so we don't have any outstanding data to output. if (extra_info) print_msg(".. so there's no need to output any of this" " packet\n"); return 0; } // We need to output whatever came before the terminating item if (extra_info) fprint_msg(".. so need to output %d bytes of this" " packet\n",item_start.inpacket); err = write_ES_as_TS_PES_packet(output, reader->packet->es_data, item_start.inpacket, reader->output_video_pid, DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error flushing start of PES packet after normal play\n"); return 1; } return 0; } /* * Output the next reference picture. If `I_only` then only output the next * I (or IDR) picture. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. */ static int output_next_reference_picture(stream_context stream, TS_writer_p output, int verbose, int quiet, int I_only) { int err; picture picture; if (extra_info) print_msg(".. outputting next reference picture\n"); for (;;) { err = get_next_picture(stream,verbose,quiet,&picture); if (err == EOF) { // Clearly there is no next picture - so we can't output it if (extra_info) print_msg("End of file\n"); return err; } else if (err) { print_err("### Error trying to resynchronise after fast forward\n"); return 1; } if (extra_info) { print_msg(".. read next picture: "); print_picture(picture); print_msg("\n"); } if (is_non_frame(picture)) { // A sequence header doesn't help us directly, but we can output // it as it will in practise be followed by an I picture // A sequence end will be followed by a sequence header, so we can // treat it similarly if (extra_info) print_msg(".. writing it out\n"); err = write_picture_as_TS(stream,output,picture); if (err) { print_err("### Error writing out picture list\n"); free_picture(&picture); return 1; } } else if (( I_only && is_I_or_IDR_picture(picture)) || (!I_only && is_reference_picture(picture))) { if (extra_info) print_msg(".. picture acceptable\n"); break; } free_picture(&picture); } // So we've got something sensible to continue with // - don't forget to write it out! if (extra_info) print_msg(".. writing it out\n"); err = write_picture_as_TS(stream,output,picture); if (err) { print_err("### Error writing out picture list\n"); free_picture(&picture); return 1; } free_picture(&picture); return 0; } /* * Resynchronise after reverse, ready for forwards playing (at whatever speed) * * Always call this immediately after reversing. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int resync_after_reverse(stream_context stream, TS_writer_p output, int verbose, int quiet) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); if (extra_info) print_msg(" \nResynchronising PES packets after reverse\n"); // When reversing, data is read directly from the required locations // in the input file, without using the normal "get next picture" // mechanisms. // // When we *stop* reversing, we need to "pretend" to have read to the // (end of the) last picture output by the normal mechanisms // Undo any memory of previous pictures/context reset_stream(stream); if (extra_info) fprint_msg(" triple byte = %02x,%02x,%02x, next byte to be from " OFFSET_T_FORMAT "/%d\n", es->prev2_byte,es->prev1_byte,es->cur_byte, es->posn_of_next_byte.infile, es->posn_of_next_byte.inpacket); // @@@ The following is not true, methinks, as we've been outputting IDR // and I frames (since there are not enough I frames, in hp-trail // at least). On the other hand, there's not much we can do about it. // If we've just been reversing H.264 data, we know we've just output an // IDR, and we also know that IDRs act as "backstops" for B pictures - they // can't refer "through" them. Thus we don't need to worry about outputting // anything extra. // @@@ Even for H.264, it may be safer to output another reference picture, // and it does help get the internal datastructures back in synch. //if (!stream.is_h262) // return 0; // However, if it's H.262 data, we know we've just output a reference // picture (specifically, an I picture), but that we can't safely output // a B picture until we've output the *next* reference picture, since B // pictures need to refer "back" (in decoding order - back and forwards // in "play" order) to two reference pictures. err = output_next_reference_picture(stream,output,verbose,quiet,FALSE); if (err == EOF) return EOF; else if (err) { print_err("### Error outputting next reference picture," " after reversing\n"); return 1; } return 0; } /* * "Rewind" to the start of our stream, ready to start again from the * beginning. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int rewind_stream(stream_context stream) { if (extra_info) print_msg(" \nRewinding\n"); if (stream.is_h262) return rewind_h262_context(stream.u.h262); else return rewind_access_unit_context(stream.u.h264); } /* * Resynchronise playing after fast fast forwarding. Always call this when * changing from fast forward back to normal speed playing. We want to * be in video only mode for this function. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int resync_after_filter(stream_context stream, TS_writer_p output, int verbose, int quiet) { int err; if (extra_info) print_msg(" \nResynchronising after fast fast forward\n"); // Fast forwarding with "filter" drops reference frames. // B pictures refer "back" (in decoding order) to two the last two // reference frames (although, in H.264 an IDR acts as a "stop" to this). // // If we've just been filtering, we know we just output *some* reference // picture, but we probably (almost certainly) hadn't output the preceding // reference picture. // // Specifically, typical data might be laid out as (using the output // of esdots, slightly massaged):: // // [E>iE bE bE pE bE bE pE bE bE ... // // get_next_filtered_picture() returns us the "i" picture, and if we // then get the 'obvious' next data, we'll end up with a "b" picture, // which is not what we want. Thus we need to read forwards until // we reach the next "i" or "p" picture (in this case, it would be the // next "p" picture). // @@@ For H.264, it might perhaps make more sense to "reverse" to the // last IDR, output *that*, and then just continue playing. We know that // B pictures can't refer backwards "through" an IDR. This might also // *look* better when we've finished fast forwarding... // So... err = output_next_reference_picture(stream,output,verbose,quiet,FALSE); if (err == EOF) return EOF; else if (err) { print_err("### Error outputting next reference picture," " after fast forwarding\n"); return 1; } return 0; } /* * Resynchronise playing ready for normal playing again. * * Call this with server output off, but turn it on again immediately * after this call. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int back_to_normal(stream_context stream, TS_writer_p output, int tsdirect) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; ES_offset item_start; if (extra_info) print_msg(" \nResynchronising PES packets for normal play\n"); if (reader->packet == NULL) { // We appear to have reached the end of file - there's not much // we can do about this, nor (probably) should we return 0; } if (!tsdirect) { // It can't hurt to reiterate the program data, and if we were just // playing a different program stream, it's a good idea err = write_program_data(reader,output); if (err) return err; } // When playing forwards at normal speed, each PES packet is read in, // processed to extract information, and then (automatically) written // out again when the next PES packet is read in. // // However, if we have been fast forwarding (at whatever speed), then we // will have been outputting only some pictures, and not outputting PES // packets automatically. // // In this case, we need to make it appear as if the (rest of the) // current PES packet had been output by the automatic mechanisms. // // In this context, "the rest of the PES packet" means all the data // (in this PES packet) from the start of the H.262 item that stopped us // reading the last picture // // However, if we have instead been reversing, then we do not have a last // item (since reversing just outputs uninterpreted chunks of data). We do, // however, still know the first byte of the next piece of information after // that chunk of data, and that should be enough. if (stream.is_h262) { if (stream.u.h262->last_item == NULL) { if (extra_info) print_msg(".. no H.262 last item, presumably been" " reversing\n"); item_start = es->posn_of_next_byte; // In which case, we've already output the data for our "last" item // and only some of the following cases can occur... } else { // The ES item that comes after (and thus marks the end of) the last // picture *starts* at: item_start = stream.u.h262->last_item->unit.start_posn; } } else { if (stream.u.h264->pending_nal == NULL) { // Either we ended the previous access unit for some reason that // didn't need to read the next NAL unit, or we've been reversing // (or we just started and there was no previous access unit) item_start = es->posn_of_next_byte; } else { // The previous access unit was ended by this "pending" NAL unit, // so the "next" item presumably starts with that... item_start = stream.u.h264->pending_nal->unit.start_posn; } } if (extra_info) { fprint_msg(".. posn_of_next_byte is " OFFSET_T_FORMAT "/%d\n", es->posn_of_next_byte.infile,es->posn_of_next_byte.inpacket); if (stream.is_h262) { if (stream.u.h262->last_item) { fprint_msg(" last item starts at " OFFSET_T_FORMAT "/%d,\n", stream.u.h262->last_item->unit.start_posn.infile, stream.u.h262->last_item->unit.start_posn.inpacket); print_data(TRUE," last item", stream.u.h262->last_item->unit.data, stream.u.h262->last_item->unit.data_len,20); } } else { if (stream.u.h264->pending_nal) { fprint_msg(" last item starts at " OFFSET_T_FORMAT "/%d,\n", stream.u.h264->pending_nal->unit.start_posn.infile, stream.u.h264->pending_nal->unit.start_posn.inpacket); print_data(TRUE," pending NAL unit", stream.u.h264->pending_nal->unit.data, stream.u.h264->pending_nal->unit.data_len,20); } } fprint_msg(".. i.e., last item starts at " OFFSET_T_FORMAT "/%d\n", item_start.infile,item_start.inpacket); fprint_msg(" PES ES data length is %d\n" " difference is %d\n", reader->packet->es_data_len, reader->packet->es_data_len-item_start.inpacket); fprint_msg(" reader->packet->posn is " OFFSET_T_FORMAT "\n", reader->packet->posn); } if (item_start.infile < reader->packet->posn) { // Said last item started in the previous PES packet // - we need to output the part of it that is in that previous packet // Given the next byte to be read (from this PES packet) int32_t curposn = es->posn_of_next_byte.inpacket; // we can work out how much of the item was in the previous packet // (sanity check - if the next byte to read was 1, then we've read one // byte from the current packet, and the following should indeed be right // - look at pes.c:read_PES_ES_byte and es.c:next_triple_byte for details) int32_t length_wanted; if (stream.is_h262) { length_wanted = stream.u.h262->last_item->unit.data_len - curposn; if (extra_info) fprint_msg(".. next byte is %d, so length wanted is %d" " - outputting it\n",curposn,length_wanted); err = write_ES_as_TS_PES_packet(output, stream.u.h262->last_item->unit.data, length_wanted, reader->output_video_pid, DEFAULT_VIDEO_STREAM_ID); } else { // @@@ For H.264, do we know, when we get here, that we always // have a pending NAL unit? length_wanted = stream.u.h264->pending_nal->unit.data_len - curposn; if (extra_info) fprint_msg(".. next byte is %d, so length wanted is %d" " - outputting it\n",curposn,length_wanted); err = write_ES_as_TS_PES_packet(output, stream.u.h264->pending_nal->unit.data, length_wanted, reader->output_video_pid, DEFAULT_VIDEO_STREAM_ID); } if (err) { print_err("### Error flushing (start of) last item after fast forward\n"); return 1; } // That leaves us with the whole of this packet still to output, // and we can leave that to the automated mechanism next time it // reads in a new PES packet } else if (item_start.inpacket == 0) { // Said last item started at the start of this PES packet // so there's nothing to flush, and we can leave the automated // mechanism to sort out this packet, as above if (extra_info) print_msg(".. i.e., at start of packet, nothing to do\n"); } else { // Said last item starts part way through this PES packet int32_t start_offset = item_start.inpacket; int32_t length_wanted = reader->packet->es_data_len - start_offset; if (extra_info) { fprint_msg(".. so output %d bytes at end of PES packet\n",length_wanted); print_data(TRUE,".. end bytes",&reader->packet->es_data[start_offset], length_wanted,20); } err = write_ES_as_TS_PES_packet(output, &reader->packet->es_data[start_offset], length_wanted, reader->output_video_pid, DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error flushing rest of PES packet after fast forward\n"); return 1; } // That's all very well, but when the server restarts, and a call is made // to read (the next) PES packet in, it will attempt to write *this* PES // packet out again. So tell it not to do that... reader->dont_write_current_packet = TRUE; } return 0; } /* * Read PES packets and write them out to the target, fast forward. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int play_stripped(stream_context stream, filter_context fcontext, TS_writer_p output, int verbose, int quiet, int tsdirect, int num_fast, int with_seq_hdrs) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; int total_pictures = 0; // stop_server_output(reader); // And then reset our filter context so that we start filtering without // remembering anything about last time we filtered reset_filter_context(fcontext,0); if (!tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } if (extra_info) print_msg("Fast forwarding (strip)\n"); for (;;) { picture this_picture; picture seq_hdr; // H.262 only - *we* mustn't free this one int delta_pictures_seen; if (tswrite_command_changed(output)) return COMMAND_RETURN_CODE; err = get_next_stripped(fcontext,verbose,quiet, &seq_hdr,&this_picture,&delta_pictures_seen); if (err == EOF || err == COMMAND_RETURN_CODE) { return err; } else if (err) { print_err("### Error getting next stripped picture\n"); return 1; } if (with_seq_hdrs && !is_null_picture(seq_hdr)) { err = write_picture_as_TS(stream,output,seq_hdr); if (err) { print_err("### Error writing out sequence header\n"); free_picture(&this_picture); return 1; } } err = write_picture_as_TS(stream,output,this_picture); if (err) { print_err("### Error writing out picture list\n"); free_picture(&this_picture); return 1; } free_picture(&this_picture); total_pictures += delta_pictures_seen; if (num_fast > 0 && total_pictures > num_fast) break; } return 0; } /* * Read PES packets and write them out to the target, fast fast forward. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int play_filtered(stream_context stream, filter_context fcontext, TS_writer_p output, int verbose, int quiet, int tsdirect, int num_faster, int frequency, int with_seq_hdrs) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; picture this_picture; picture last_picture; picture seq_hdr; // H.262 only - *we* mustn't free this one int total_pictures = 0; // stop_server_output(reader); // Reset our filter context so that we start filtering without remembering // anything about last time we filtered reset_filter_context(fcontext,frequency); if (!tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } if (extra_info) print_msg("Fast forwarding (filter)\n"); unset_picture(stream.is_h262,&this_picture); unset_picture(stream.is_h262,&last_picture); for (;;) { int delta_pictures_seen; if (tswrite_command_changed(output)) { free_picture(&last_picture); err = COMMAND_RETURN_CODE; break; } err = get_next_filtered(fcontext,verbose,quiet, &seq_hdr,&this_picture,&delta_pictures_seen); if (err == EOF || err == COMMAND_RETURN_CODE) { free_picture(&last_picture); break; } else if (err) { print_err("### Error getting next filtered picture\n"); free_picture(&last_picture); return 1; } if (is_null_picture(this_picture)) { // We need to repeat the last picture this_picture = last_picture; unset_picture(stream.is_h262,&last_picture); } if (!is_null_picture(this_picture)) { if (with_seq_hdrs && !is_null_picture(seq_hdr)) { err = write_picture_as_TS(stream,output,seq_hdr); if (err) { print_err("### Error writing out sequence header\n"); free_picture(&this_picture); free_picture(&last_picture); return 1; } } err = write_picture_as_TS(stream,output,this_picture); if (err) { print_err("### Error writing out picture\n"); free_picture(&this_picture); free_picture(&last_picture); return 1; } } free_picture(&last_picture); last_picture = this_picture; total_pictures += delta_pictures_seen; if (num_faster > 0 && total_pictures > num_faster) break; } // We *do* end up here if we run out of for loop... free_picture(&last_picture); if (err == EOF) { // If we reached the end of the file, then back up to the final // picture in the reversing arrays, and output that (so that the // user *sees* that final picture). // // (The last picture in the reversing arrays will be the last I or IDR // frame. We know that we are only outputting I or IDR frames, so we // know that this would also be the last frame we'd have considered // outputting. It's possible we've already output it, but on the whole // that shouldn't be terribly obvious to the user, I think.) reverse_data_p reverse_data = EXTRACT_REVERSE_FROM_STREAM(stream); // Try going back 2 I/IDR pictures... err = output_from_reverse_data_as_TS(es,output,verbose,quiet,2, reverse_data); if (err && err != COMMAND_RETURN_CODE) { print_err("### Error outputting 'last' picture at EOF\n"); return err; } // Which means we need to adjust back to normal playing *this* way err = resync_after_reverse(stream,output,verbose,quiet); if (err) return err; // Let the caller know what we did/where we are return EOF; } else { // Adjust back to normal playing err = resync_after_filter(stream,output,verbose,quiet); if (err) return err; } return 0; } /* * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * - `num_to_skip` is the number of frames to skip * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int skip_forwards(stream_context stream, TS_writer_p output, filter_context fcontext, int with_seq_hdrs, int num_to_skip, int verbose, int quiet, int tsdirect) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; picture this_picture; picture seq_hdr; // H.262 only - *we* mustn't free this one int delta_pictures_seen; #if TIME_SKIPPING time_t start_time,end_time; clock_t start_clock,end_clock; start_time = time(NULL); start_clock = clock(); #endif // Reset our filter context so that we start filtering without remembering // anything about last time we filtered reset_filter_context(fcontext,num_to_skip); if (!tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } if (extra_info) fprint_msg("Skipping forwards (%d frames)\n",num_to_skip); unset_picture(stream.is_h262,&this_picture); // Say that we don't want our skipping to be interrupted by the next command tswrite_set_command_atomic(output,TRUE); err = get_next_filtered(fcontext,verbose,quiet, &seq_hdr,&this_picture,&delta_pictures_seen); if (err && err != EOF) { tswrite_set_command_atomic(output,FALSE); if (err == COMMAND_RETURN_CODE) return err; else { print_err("### Error skipping pictures\n"); return 1; } } if (err == EOF) { // We hit the end of file before finding anything - so we should make // sure to display the "last" picture (actually, the last I/IDR picture) // Luckily, we can do that by "reversing" to it... reverse_data_p reverse_data = EXTRACT_REVERSE_FROM_STREAM(stream); // Try going back 2 I/IDR pictures... err = output_from_reverse_data_as_TS(es,output,verbose,quiet,2, reverse_data); if (err) { print_err("### Error outputting 'last' picture at EOF\n"); tswrite_set_command_atomic(output,FALSE); return err; } // Which means we need to adjust back to normal playing *this* way err = resync_after_reverse(stream,output,verbose,quiet); if (err) { tswrite_set_command_atomic(output,FALSE); return err; } // Let the caller know what we did/where we are return EOF; } else { // Since we're only skipping once, we shouldn't get a NULL (repeat) // picture back if (is_null_picture(this_picture)) { print_err("### Skipping returned a NULL picture\n"); free_picture(&this_picture); tswrite_set_command_atomic(output,FALSE); return 1; } if (with_seq_hdrs && !is_null_picture(seq_hdr)) { err = write_picture_as_TS(stream,output,seq_hdr); if (err) { print_err("### Error writing out sequence header\n"); free_picture(&this_picture); tswrite_set_command_atomic(output,FALSE); return 1; } } err = write_picture_as_TS(stream,output,this_picture); if (err) { print_err("### Error writing out picture\n"); free_picture(&this_picture); tswrite_set_command_atomic(output,FALSE); return 1; } free_picture(&this_picture); // And remember to adjust back to normal playing err = resync_after_filter(stream,output,verbose,quiet); if (err) { tswrite_set_command_atomic(output,FALSE); return 1; } } #if TIME_SKIPPING end_clock = clock(); end_time = time(NULL); fprint_msg("Started skipping at %s",ctime(&start_time)); fprint_msg("Finished skipping at %s",ctime(&end_time)); fprint_msg("Elapsed time %.3fs\n",difftime(end_time,start_time)); fprint_msg("Process time %.3fs\n", ((double)(end_clock-start_clock)/CLOCKS_PER_SEC)); #endif // Remember to allow future commands to be interrupted tswrite_set_command_atomic(output,FALSE); return 0; } /* * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * - `num_to_skip` is the number of frames to skip * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int skip_backwards(stream_context stream, TS_writer_p output, int num_to_skip, int verbose, int quiet, int tsdirect, reverse_data_p reverse_data) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; if (!tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } if (extra_info) fprint_msg("Skipping backwards (%d frames)\n",num_to_skip); // Say that we don't want our skipping to be interrupted by the next command tswrite_set_command_atomic(output,TRUE); err = output_in_reverse_as_TS(es,output,num_to_skip,verbose,quiet, -1,num_to_skip,reverse_data); if (err && err != COMMAND_RETURN_CODE) { print_err("### Error skipping backwards\n"); tswrite_set_command_atomic(output,FALSE); return err; } err = resync_after_reverse(stream,output,verbose,quiet); if (err) { tswrite_set_command_atomic(output,FALSE); return err; } // Remember to allow future commands to be interrupted tswrite_set_command_atomic(output,FALSE); return 0; } /* * Write pictures out to the target, in reverse * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int play_reverse(stream_context stream, TS_writer_p output, int verbose, int quiet, int tsdirect, int frequency, int num_reverse, reverse_data_p reverse_data) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; if (extra_info) print_msg("Reversing\n"); if (tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } #if SHOW_REVERSE_DATA if (extra_info) { int ii; for (ii=0; iilength; ii++) if (stream.is_h262 && reverse_data->seq_offset[ii] == 0) fprint_msg("%3d: seqh at " OFFSET_T_FORMAT "/%d for %d\n", ii, reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); else fprint_msg("%3d: %4d at " OFFSET_T_FORMAT "/%d for %d\n", ii,reverse_data->index[ii], reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); } #endif err = output_in_reverse_as_TS(es,output,frequency,verbose,quiet, -1,num_reverse,reverse_data); if (err && err != COMMAND_RETURN_CODE) { print_err("### Error outputting reversed data\n"); return err; } // Adjust back to normal playing err = resync_after_reverse(stream,output,verbose,quiet); if (err) return err; return err; } /* * Read PES packets and write them out to the target, obeying user * commands as to what to do. * * Returns 0 if all went well, EOF if the 'q'uit command has been given, * 1 if an error occurred. */ static int obey_command(char this_command, char last_command, int *index, int started[MAX_INPUT_FILES], PES_reader_p reader[MAX_INPUT_FILES], stream_context stream[MAX_INPUT_FILES], filter_context fcontext[MAX_INPUT_FILES], filter_context scontext[MAX_INPUT_FILES], reverse_data_p reverse_data[MAX_INPUT_FILES], TS_writer_p tswriter, int video_only, int verbose, int quiet, int tsdirect, int with_seq_hdrs, int ffrequency, int rfrequency) { int err = 0; int new_stream; int which = *index; // which stream we're reading // Loop obeying our given command and any "imaginary" commands that // result therefrom for (;;) { #ifdef DEBUG_COMMANDS fprint_msg("__ obeying command '%c'\n",this_command); #endif switch (this_command) { case COMMAND_NORMAL: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Forwards, normal speed\n", tswriter->where.socket,which); if (last_command != COMMAND_NORMAL && started[which]) { err = back_to_normal(stream[which],tswriter,tsdirect); if (err) return 1; } started[which] = TRUE; set_PES_reader_video_only(reader[which],video_only); err = play_normal(stream[which],tswriter,verbose,quiet,0, tsdirect, reverse_data[which]); // If we've had a new command, and it's not 'n' again... if (err == COMMAND_RETURN_CODE && tswriter->command != COMMAND_NORMAL) err = flush_after_normal(stream[which],tswriter,verbose,quiet); break; case COMMAND_PAUSE: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Pause\n", tswriter->where.socket,which); stop_server_output(reader[which]); err = wait_for_command(tswriter); break; case COMMAND_FAST: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Fast forwards\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = play_stripped(stream[which],scontext[which],tswriter, verbose,quiet,tsdirect,0,with_seq_hdrs); break; case COMMAND_FAST_FAST: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Fast fast forwards\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = play_filtered(stream[which],fcontext[which],tswriter, verbose,quiet,tsdirect,0,ffrequency,with_seq_hdrs); break; case COMMAND_REVERSE: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Reverse\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = play_reverse(stream[which],tswriter,verbose,quiet, tsdirect, rfrequency,0,reverse_data[which]); if (err == 0) { if (!quiet) fprint_msg("Start of file %d\n",which); this_command = COMMAND_PAUSE; break; } break; case COMMAND_FAST_REVERSE: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Reverse (faster)\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = play_reverse(stream[which],tswriter,verbose,quiet, tsdirect, 2*rfrequency,0,reverse_data[which]); if (err == 0) { if (!quiet) fprint_msg("Start of file %d\n",which); this_command = COMMAND_PAUSE; break; } break; case COMMAND_SKIP_FORWARD: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Skip forwards 10 seconds\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = skip_forwards(stream[which],tswriter, fcontext[which],with_seq_hdrs, SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect); this_command = COMMAND_NORMAL; // aim to continue with normal play break; case COMMAND_SKIP_BACKWARD: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Skip backwards 10 seconds\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = skip_backwards(stream[which],tswriter,SMALL_SKIP_DISTANCE, verbose,quiet,tsdirect,reverse_data[which]); this_command = COMMAND_NORMAL; // aim to continue with normal play break; case COMMAND_SKIP_FORWARD_LOTS: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Skip forwards 3 minutes\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = skip_forwards(stream[which],tswriter, fcontext[which],with_seq_hdrs, BIG_SKIP_DISTANCE,verbose,quiet,tsdirect); this_command = COMMAND_NORMAL; // aim to continue with normal play break; case COMMAND_SKIP_BACKWARD_LOTS: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Skip backwards 3 minutes\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = skip_backwards(stream[which],tswriter,BIG_SKIP_DISTANCE, verbose,quiet,tsdirect,reverse_data[which]); this_command = COMMAND_NORMAL; // aim to continue with normal play break; case COMMAND_SELECT_FILE_0: new_stream = 0; goto change_stream; case COMMAND_SELECT_FILE_1: new_stream = 1; goto change_stream; case COMMAND_SELECT_FILE_2: new_stream = 2; goto change_stream; case COMMAND_SELECT_FILE_3: new_stream = 3; goto change_stream; case COMMAND_SELECT_FILE_4: new_stream = 4; goto change_stream; case COMMAND_SELECT_FILE_5: new_stream = 5; goto change_stream; case COMMAND_SELECT_FILE_6: new_stream = 6; goto change_stream; case COMMAND_SELECT_FILE_7: new_stream = 7; goto change_stream; case COMMAND_SELECT_FILE_8: new_stream = 8; goto change_stream; case COMMAND_SELECT_FILE_9: new_stream = 9; goto change_stream; change_stream: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Select file\n", tswriter->where.socket,new_stream); if (reader[new_stream] == NULL) { fprint_msg(".. No input file defined for stream %d - ignored\n", new_stream); } else { #if 0 // The following would only make sense if we *knew* we'd just been doing 'n'ormal play... // Try to ensure we finish at the end of a picture... err = flush_after_normal(stream[which],tswriter,verbose,quiet); if (err && err != COMMAND_RETURN_CODE && err != EOF) return err; #endif // Pause the current stream stop_server_output(reader[which]); // Change to the new stream *index = which = new_stream; // @@@ For the moment, changing channel also means rewinding // the "new" channel. This can become a "pure" channel change // when we have the ability to "go back one(ish) reverse item(s)" // and guarantee to end up at a sensible place to continue from // (an IDR for H.264, or a GOP for H.262) // Rewind it err = rewind_stream(stream[which]); if (err) return 1; // And note that we *are* starting from the beginning again started[which] = FALSE; } // And return to normal playing this_command = COMMAND_NORMAL; break; case COMMAND_QUIT: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Quitting\n", which,tswriter->where.socket); return EOF; default: fprint_err("!!! Command '%c' ignored\n",this_command); this_command = COMMAND_NORMAL; break; } // Work out what to do next switch (err) { case 0: case COMMAND_RETURN_CODE: break; case EOF: if (!quiet) fprint_msg("End of file %d\n",which); this_command = COMMAND_PAUSE; break; default: fprint_err("!!! Error playing file %d - pausing\n",which); this_command = COMMAND_PAUSE; break; // return 1; } if (tswriter->command_changed) return 0; } } /* * Read PES packets and write them out to the target, obeying user * commands as to what to do. * * Returns 0 if all went well, 1 if an error occurred. */ static int play(int default_index, PES_reader_p reader[MAX_INPUT_FILES], stream_context stream[MAX_INPUT_FILES], filter_context fcontext[MAX_INPUT_FILES], filter_context scontext[MAX_INPUT_FILES], reverse_data_p reverse_data[MAX_INPUT_FILES], TS_writer_p tswriter, int video_only, int verbose, int quiet, int tsdirect, int with_seq_hdrs, int ffrequency, int rfrequency) { int err; int ii; int started[MAX_INPUT_FILES]; int which = default_index; // which stream we're reading // Any function which writes to the output may read a new command character, // but only if tswriter->command_changed is FALSE. Such a function will then // return COMMAND_RETURN_CODE. // When a new command character is read, tswriter->command_changed is set to // TRUE. It is up to us to set it back to FALSE when we have finished // dealing with the new command letter. byte this_command = tswriter->command; byte last_command = COMMAND_NOT_A_COMMAND; for (ii=0; iicommand_changed = FALSE; this_command = tswriter->command; #ifdef DEBUG_COMMANDS fprint_msg("xx Command is '%c', last command '%c'\n", this_command,last_command); #endif err = obey_command(this_command,last_command,&which, started,reader,stream,fcontext,scontext,reverse_data, tswriter,video_only,verbose,quiet,tsdirect,with_seq_hdrs, ffrequency,rfrequency); if (err == EOF) return 0; // The user gave the 'q'uit command else if (err) { print_err("### Error terminated play\n"); return 1; } last_command = this_command; } } /* * Read PES packets and write them out to the target, obeying user * commands as to what to do. * * Returns 0 if all went well, 1 if an error occurred. */ static int play_pes_packets(PES_reader_p reader[MAX_INPUT_FILES], TS_writer_p tswriter, tsserve_context_p context, int verbose, int quiet) { int err; int ii; ES_p es[MAX_INPUT_FILES]; // A view of our PES packets as ES units reverse_data_p reverse_data[MAX_INPUT_FILES]; stream_context stream[MAX_INPUT_FILES]; filter_context fcontext[MAX_INPUT_FILES]; filter_context scontext[MAX_INPUT_FILES]; if (!quiet) print_msg("\nSetting up environment\n"); // Request that packets be written out to the TS writer as a "side effect" of // reading them in. The default is to write PES packets (just for the video // and audio data), but the alternative is to write all TS packets (if the // data *is* TS) for (ii = 0; ii < MAX_INPUT_FILES; ii++) { if (reader[ii] != NULL) { set_server_output(reader[ii],tswriter,!context->tsdirect, context->repeat_program_every); set_server_padding(reader[ii],context->pes_padding); } } for (ii = 0; ii < MAX_INPUT_FILES; ii++) { es[ii] = NULL; reverse_data[ii] = NULL; // Closing uninitialised things is a bit dodgy if we don't indicate // what *type* of unset value is being used. However, in practice // it doesn't matter much, as both the H.262 and H.264 "destroy" // functions for streams and filter contexts sensibly do nothing // with a NULL value - so we might as well just say the same for all... stream[ii].is_h262 = fcontext[ii].is_h262 = scontext[ii].is_h262 = FALSE; stream[ii].u.h262 = NULL; fcontext[ii].u.h262 = scontext[ii].u.h262 = NULL; } // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; iipad_start; ii++) { err = write_TS_null_packet(tswriter); if (err) return 1; } // And sort out our stack-of-streams atop each input file for (ii = 0; ii < MAX_INPUT_FILES; ii++) { if (reader[ii] == NULL) continue; if (!quiet) fprint_msg("Setting up stream %d\n",ii); // Wrap our PES stream up as an ES stream // Note that this has the side-effect of reading the first packet // from the file (so that the ES reader can prime its 3-byte buffer). // This means that we will have read in the first PES packet, and // thus (for TS data) potentially quite a few TS packets, which // may also have included PAT/PMT. Luckily, we rely upon our caller // to have aleady set up PES or TS mirroring. err = build_elementary_stream_PES(reader[ii],&es[ii]); if (err) { fprint_err("### Error trying to build ES reader for PES reader %d\n",ii); goto tidy_up; } // Put an access unit or H.262 unit context around that err = build_stream(es[ii],!(reader[ii]->is_h264),ii+1,&stream[ii]); if (err) { fprint_err("### Unable to build input stream %d\n",ii); goto tidy_up; } // Build our reverse memory datastructure err = build_and_attach_reverse(stream[ii],&reverse_data[ii]); if (err) { fprint_err("### Unable to build reverse memory for stream %d\n",ii); goto tidy_up; } // Tell it what PID and stream id to use when outputting reversed data set_reverse_pid(reverse_data[ii],reader[ii]->output_video_pid, DEFAULT_VIDEO_STREAM_ID); if (!context->with_seq_hdrs) reverse_data[ii]->output_sequence_headers = FALSE; // Build our fast forwards filter contexts err = build_filter_context(stream[ii],FALSE,context->ffrequency,&fcontext[ii]); if (err) { fprint_err("### Unable to build filter context for stream %d\n",ii); goto tidy_up; } err = build_filter_context(stream[ii],TRUE,0,&scontext[ii]); if (err) { fprint_err("### Unable to build strip context for stream %d\n",ii); goto tidy_up; } } // And, at last, do what we came for err = play(context->default_file_index,reader,stream,fcontext,scontext,reverse_data, tswriter,context->video_only,verbose,quiet,context->tsdirect, context->with_seq_hdrs,context->ffrequency,context->rfrequency); tidy_up: for (ii = 0; ii < MAX_INPUT_FILES; ii++) { close_elementary_stream(&es[ii]); free_reverse_data(&reverse_data[ii]); close_stream(stream[ii]); free_filter_context(fcontext[ii]); free_filter_context(scontext[ii]); } return err; } /* * Read PES packets and write them out to the target. Alternate normal * speed, fast forward and reverse (in some sequence). * * Returns 0 if all went well, 1 if an error occurred. */ static int test_play(PES_reader_p reader, stream_context stream, filter_context fcontext, filter_context scontext, reverse_data_p reverse_data, TS_writer_p tswriter, int video_only, int verbose, int quiet, int tsdirect, int num_normal, int num_fast, int num_faster, int num_reverse, int ffrequency, int rfrequency, int with_seq_hdrs) { int err = 0; int started = FALSE; int ii; if (num_fast == 0 && num_faster == 0 && num_reverse == 0) { // Special case -- just play through print_msg(">> Just playing at normal speed\n"); set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,0,reverse_data); if (err == EOF) return 0; else return err; } print_msg(">> Going through sequence twice\n"); for (ii=0; ii<2; ii++) { // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); if (started) { err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; } started = TRUE; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; stop_server_output(reader); // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Fast forward for %d\n",num_fast); set_PES_reader_video_only(reader,TRUE); err = play_stripped(stream,scontext,tswriter,verbose,quiet,tsdirect, num_fast, with_seq_hdrs); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; stop_server_output(reader); // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Faster forward for %d\n",num_faster); set_PES_reader_video_only(reader,TRUE); err = play_filtered(stream,fcontext,tswriter,verbose,quiet,tsdirect, num_faster, ffrequency,with_seq_hdrs); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; stop_server_output(reader); // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Reverse for %d\n",num_reverse); set_PES_reader_video_only(reader,TRUE); err = play_reverse(stream,tswriter,verbose,quiet,rfrequency, tsdirect, num_reverse,reverse_data); if (err == EOF) break; else if (err) return 1; } if (verbose || extra_info) print_msg("\n \n"); if (err == EOF) print_msg("** End of file\n"); else print_msg(">> End of sequences\n"); return 0; } /* * Read PES packets and write them out to the target. Test skipping forwards * and back. * * Returns 0 if all went well, 1 if an error occurred. */ static int test_skip(PES_reader_p reader, stream_context stream, filter_context fcontext, filter_context scontext, reverse_data_p reverse_data, TS_writer_p tswriter, int video_only, int verbose, int quiet, int tsdirect, int with_seq_hdrs) { int err = 0; int num_normal = 100; int started = FALSE; int ii; print_msg(">> Going through sequence once\n"); for (ii=0; ii<1; ii++) { fprint_msg("\n>> Iteration %d\n\n",ii); // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); if (started) { err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; } started = TRUE; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip forwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs, SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip forwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs, SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip backwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip backwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip forwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs, SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip backwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data); if (err == EOF) break; else if (err) return 1; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ num_normal = 100; if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) { print_msg("** End of file\n"); return 0; } else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) { print_msg("** End of file\n"); return 0; } else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); if (err == EOF) print_msg("** End of file\n"); else print_msg(">> End of sequences\n"); return 0; } /* * Read PES packets and write them out to the target. Alternate normal * speed, fast forward and reverse (in some sequence). * * Returns 0 if all went well, 1 if an error occurred. */ static int test_play_pes_packets(PES_reader_p reader, TS_writer_p tswriter, tsserve_context_p context, int pad_start, int video_only, int verbose, int quiet, int tsdirect, int num_normal, int num_fast, int num_faster, int num_reverse, int ffrequency, int rfrequency, int skiptest, int with_seq_hdrs) { int err; int ii; ES_p es; // A view of our PES packets as ES units reverse_data_p reverse_data = NULL; stream_context stream; filter_context fcontext; filter_context scontext; // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; iitsdirect, context->repeat_program_every); set_server_padding(reader,context->pes_padding); // Wrap our PES stream up as an ES stream err = build_elementary_stream_PES(reader,&es); if (err) { print_err("### Error trying to build ES reader from PES reader\n"); return 1; } // Build our reverse memory datastructure err = build_reverse_data(&reverse_data,reader->is_h264); if (err) { print_err("### Unable to build reverse memory\n"); close_elementary_stream(&es); return 1; } stream.is_h262 = fcontext.is_h262 = scontext.is_h262 = !(reader->is_h264); if (reader->is_h264) { access_unit_context_p acontext; // Our ES data as access units h264_filter_context_p fcontext4 = NULL; // And a filter over that h264_filter_context_p scontext4 = NULL; // And another err = build_access_unit_context(es,&acontext); if (err) { print_err("### Error trying to build access unit reader from ES reader\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); return 1; } add_access_unit_reverse_context(acontext,reverse_data); err = build_h264_filter_context(&fcontext4,acontext,ffrequency); if (err) { print_err("### Unable to build filter context\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); free_access_unit_context(&acontext); return 1; } err = build_h264_filter_context_strip(&scontext4,acontext,TRUE); if (err) { print_err("### Unable to build strip context\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); free_access_unit_context(&acontext); free_h264_filter_context(&fcontext4); return 1; } stream.u.h264 = acontext; fcontext.u.h264 = fcontext4; scontext.u.h264 = scontext4; if (skiptest) err = test_skip(reader,stream,fcontext,scontext,reverse_data,tswriter, video_only,verbose,quiet,tsdirect,FALSE); else err = test_play(reader,stream,fcontext,scontext,reverse_data,tswriter, video_only,verbose,quiet,tsdirect, num_normal,num_fast,num_faster,num_reverse, ffrequency,rfrequency,FALSE); free_access_unit_context(&acontext); free_h264_filter_context(&fcontext4); free_h264_filter_context(&scontext4); } else { h262_context_p h262; // Our ES data as H.262 items h262_filter_context_p fcontext2 = NULL; // And a filter over that h262_filter_context_p scontext2 = NULL; // And another if (!with_seq_hdrs) reverse_data->output_sequence_headers = FALSE; err = build_h262_context(es,&h262); if (err) { print_err("### Error trying to build H.262 reader from ES reader\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); return 1; } add_h262_reverse_context(h262,reverse_data); err = build_h262_filter_context(&fcontext2,h262,ffrequency); if (err) { print_err("### Unable to build filter context\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); free_h262_context(&h262); return 1; } err = build_h262_filter_context_strip(&scontext2,h262,TRUE); if (err) { print_err("### Unable to build strip context\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); free_h262_context(&h262); free_h262_filter_context(&fcontext2); return 1; } stream.u.h262 = h262; fcontext.u.h262 = fcontext2; scontext.u.h262 = scontext2; if (skiptest) err = test_skip(reader,stream,fcontext,scontext,reverse_data,tswriter, video_only,verbose,quiet,tsdirect,with_seq_hdrs); else err = test_play(reader,stream,fcontext,scontext,reverse_data,tswriter, video_only,verbose,quiet,tsdirect, num_normal,num_fast,num_faster,num_reverse, ffrequency,rfrequency,with_seq_hdrs); free_h262_context(&h262); free_h262_filter_context(&fcontext2); free_h262_filter_context(&scontext2); } close_elementary_stream(&es); free_reverse_data(&reverse_data); return err; } static int open_input_file(tsserve_context_p context, int quiet, int verbose, PES_reader_p *reader) { int err = open_PES_reader(context->input_names[context->default_file_index], !quiet,verbose,reader); if (err) { fprint_err("### Error opening file %s\n", context->input_names[context->default_file_index]); return 1; } if (!quiet) fprint_msg("Opened input file %s (as %s)\n", context->input_names[context->default_file_index], ((*reader)->is_TS?"TS":"PS")); // If it's PS data, check if we're overriding its stream type if (!(*reader)->is_TS && context->force_stream_type && (*reader)->is_h264 == context->want_h262) { if (!quiet) fprint_msg("File appeared to contain %s, forcing %s\n", (*reader)->is_h264?"MPEG-4/AVC (H.264)":"MPEG-2 (H.272)", context->want_h262?"MPEG-2":"MPEG-4/AVC"); set_PES_reader_h264(*reader); } // If it's PS data, pretend to have read in a PAT and PMT if (!(*reader)->is_TS) { set_PES_reader_program_data(*reader,1, context->pmt_pid, context->video_pid, context->audio_pid,context->pcr_pid); set_PES_reader_dolby_stream_type(*reader,context->dolby_is_dvb); } // If we're wanting extra information, also ask to be told about // the reading and writing of underlying PES packets. (*reader)->debug_read_packets = extra_info; return 0; } static int open_input_files(tsserve_context_p context, int quiet, int verbose, PES_reader_p reader[MAX_INPUT_FILES]) { int ii; for (ii = 0; ii < MAX_INPUT_FILES; ii++) { int err; if (context->input_names[ii] == NULL) { reader[ii] = NULL; continue; } if (!quiet) fprint_msg("\nLooking at input file %d, %s\n",ii,context->input_names[ii]); err = open_PES_reader(context->input_names[ii],!quiet,verbose,&reader[ii]); if (err) { fprint_err("!!! Error opening file %d (%s)\n", ii,context->input_names[ii]); // return 1; reader[ii] = NULL; continue; } if (!quiet) fprint_msg("Opened input file %2d, %s, as %s\n",ii,context->input_names[ii], (reader[ii]->is_TS?"TS":"PS")); // If it's PS data, check if we're overriding its stream type // (for the moment, we only allow overriding of *all* files, // which is clumsy, but may be sufficient for our needs) if (!reader[ii]->is_TS && context->force_stream_type && reader[ii]->is_h264 == context->want_h262) { if (!quiet) fprint_msg("File appeared to contain %s, forcing %s\n", reader[ii]->is_h264?"MPEG-4/AVC (H.264)":"MPEG-2 (H.272)", context->want_h262?"MPEG-2":"MPEG-4/AVC"); set_PES_reader_h264(reader[ii]); } // Ensure that different input files get written out as different // programs (with differing PIDs) set_PES_reader_program_data(reader[ii], ii+1, // program number: 1 upwards DEFAULT_VIDEO_PID+ii+20, // PMT DEFAULT_VIDEO_PID+ii, // video DEFAULT_VIDEO_PID+ii+10, // audio DEFAULT_VIDEO_PID+ii); // PCR==video // If we're wanting extra information, also ask to be told about // the reading and writing of underlying PES packets. reader[ii]->debug_read_packets = extra_info; } return 0; } // ============================================================ // Serving multiple clients // ============================================================ // Arguments for passing to the child server process struct server_args { tsserve_context_p context; // Various arguments we might need TS_writer_p tswriter; // Where we're writing to int verbose; int quiet; }; static int tsserve_child_process(struct server_args *args) { int ii, err; int had_err; tsserve_context_p context = args->context; TS_writer_p tswriter = args->tswriter; int verbose = args->verbose; int quiet = args->quiet; PES_reader_p reader[MAX_INPUT_FILES]; if (!quiet) fprint_msg("Establishing connection with client on socket %d\n", tswriter->where.socket); err = tswrite_start_input(tswriter,tswriter->where.socket); if (err) { print_err("### Unable to start command input from client\n"); (void) tswrite_close(tswriter,TRUE); return 1; } err = open_input_files(context,quiet,verbose,reader); if (err) { print_err("### Unable to open input file\n"); (void) tswrite_close(tswriter,TRUE); return 1; } // And play... if (!quiet) fprint_msg("Playing to client via socket %d\n", tswriter->where.socket); err = play_pes_packets(reader,tswriter,context,verbose,quiet); if (err) { print_err("!!! Error playing PES packets to client\n"); (void) tswrite_close(tswriter,TRUE); for (ii=0;iiinput_names[ii]); had_err = TRUE; } } #ifdef _WIN32 // The original ("parent") thread does not know when we have finished, // so it cannot free the resources we are using. Of course, *we* know // we've now finished, so we can... free(args); #endif return (had_err?1:0); } #ifdef _WIN32 // ============================================================ // Windows threading ("fork" alternative) // ============================================================ /* * Wrapper for tsserve_child_process, used to coerce args, etc. */ static void child_thread_fn(void_p varg) { struct server_args *args = (struct server_args *)varg; (void) tsserve_child_process(args); } /* * Start up the child thread, to serve a single client */ static int start_child(tsserve_context_p context, TS_writer_p tswriter, int verbose, int quiet) { HANDLE child_thread; struct server_args *args; args = malloc(sizeof(struct server_args)); if (args == NULL) { print_err("### Unable to allocate memory for child datastructure\n"); return 1; } args->context = context; args->tswriter = tswriter; args->verbose = verbose; args->quiet = quiet; child_thread = (HANDLE) _beginthread(child_thread_fn,0,(void_p)args); if (child_thread == (HANDLE) -1) { fprint_err("Error creating child process: %s\n",strerror(errno)); return 1; } return 0; } #else // _WIN32 // ============================================================ // Unix forking ("thread" alternative) // ============================================================ /* * Start up the child fork, to handle the circular buffering */ static int start_child(tsserve_context_p context, TS_writer_p tswriter, int verbose, int quiet) { pid_t pid; struct server_args args = {context,tswriter,verbose,quiet}; pid = fork(); if (pid == -1) { fprint_err("Error forking: %s\n",strerror(errno)); return 1; } else if (pid == 0) { // Aha - we're the child _exit(tsserve_child_process(&args)); } tswriter->child = pid; return 0; } static void set_child_exit_handler(); /* * Signal handler - catch children and stop them becoming zombies */ static void on_child_exit() { #if 0 print_msg("sighandler: starting\n"); #endif for (;;) { int status; int pid = waitpid(-1, &status, WNOHANG); #if 0 if (pid > 0) fprint_msg("sighandler: finished with child %08x\n",pid); else fprint_msg("sighandler: finished with %d\n",pid); #endif if (pid <= 0) break; } } /* * Setup the "on child exit" signal handler */ static void set_child_exit_handler() { int ret; struct sigaction action; action.sa_handler = on_child_exit; action.sa_flags = SA_NOCLDSTOP; // we only want terminated children, not stopped children #ifdef SA_RESTART action.sa_flags |= SA_RESTART; #endif sigemptyset(&action.sa_mask); // If it goes wrong, there's not much we can do apart from grumble... #if 0 print_msg("sighandler: Setting up signal handler to reap child processes\n"); #endif ret = sigaction(SIGCHLD,&action,0); if (ret < 0) print_err("!!! tsserve: Error starting signal handler to reap child processes\n"); } #endif // _WIN32 /* * Run as a server */ static int run_server(tsserve_context_p context, int listen_port, int verbose, int quiet) { int err; SOCKET server_socket; struct sockaddr_in ipaddr; #ifdef _WIN32 err = winsock_startup(); if (err) return 1; #else set_child_exit_handler(); #endif // Create a socket. server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { #ifdef _WIN32 err = WSAGetLastError(); print_err("### Unable to create socket: "); print_winsock_err(err); print_err("\n"); #else // _WIN32 fprint_err("### Unable to create socket: %s\n",strerror(errno)); #endif // _WIN32 return 1; } // Bind it to port `listen_port` on this machine memset(&ipaddr,0,sizeof(ipaddr)); #if !defined(__linux__) && !defined(_WIN32) // On BSD, the length is defined in the datastructure ipaddr.sin_len = sizeof(struct sockaddr_in); #endif ipaddr.sin_family = AF_INET; ipaddr.sin_port = htons(listen_port); ipaddr.sin_addr.s_addr = INADDR_ANY; // any interface err = bind(server_socket,(struct sockaddr*)&ipaddr,sizeof(ipaddr)); if (err == -1) { #ifdef _WIN32 err = WSAGetLastError(); fprint_err("### Unable to bind to port %d: ",listen_port); print_winsock_err(err); print_err("\n"); #else // _WIN32 fprint_err("### Unable to bind to port %d: %s\n", listen_port,strerror(errno)); #endif // _WIN32 return 1; } for (;;) { TS_writer_p tswriter = NULL; if (!quiet) fprint_msg("\nListening for a connection on port %d" " with socket %d\n",listen_port,server_socket); #ifdef _WIN32 // tswrite_close calls winsock_cleanup(), so we need to make sure that // we call an *extra* winsock_startup to match that (and leave the // call made before this loop "in scope") err = winsock_startup(); if (err) { print_err("### Error calling winsock_startup before listening\n"); return 1; } #endif // _WIN32 err = tswrite_wait_for_client(server_socket,quiet,&tswriter); if (err) { fprint_err("### Error listening for client on port %d\n", listen_port); return 1; } if (context->drop_packets) { tswriter->drop_packets = context->drop_packets; tswriter->drop_number = context->drop_number; } err = start_child(context,tswriter,verbose,quiet); if (err) { print_err("### Error spawning child server\n"); return 1; } #if 0 // The following was a temporary fix to stop zombies without a signal handler #ifndef _WIN32 // If we've forked, then we need to free our "copy" of the tswriter err = tswrite_close(tswriter,TRUE); if (err) { print_err("### Error closing socket in parent process\n"); return 1; } #endif #endif } return 0; } /* * Run tests */ static int test_reader(tsserve_context_p context, int output_to_file, char *output_name, int port, int num_normal, int num_fast, int num_faster, int num_reverse, int skiptest, int verbose, int quiet, int tsdirect) { int err; TS_writer_p tswriter = NULL; PES_reader_p reader = NULL; err = tswrite_open((output_to_file?TS_W_FILE:TS_W_TCP), output_name,NULL,port,quiet,&tswriter); if (err) { fprint_err("### Unable to connect to %s\n",output_name); return 1; } if (context->drop_packets) { tswriter->drop_packets = context->drop_packets; tswriter->drop_number = context->drop_number; } err = open_input_file(context,quiet,verbose,&reader); if (err) { print_err("### Unable to open input file\n"); (void) tswrite_close(tswriter,TRUE); return 1; } // And play... err = test_play_pes_packets(reader,tswriter,context, context->pad_start,context->video_only, verbose,quiet,tsdirect, num_normal,num_fast,num_faster,num_reverse, context->ffrequency,context->rfrequency, skiptest,context->with_seq_hdrs); if (err) { print_err("### Error playing PES packets\n"); (void) tswrite_close(tswriter,TRUE); (void) close_PES_reader(&reader); return 1; } err = tswrite_close(tswriter,quiet); if (err) { fprint_err("### Error closing output %s: %s\n",output_name, strerror(errno)); (void) close_PES_reader(&reader); return 1; } err = close_PES_reader(&reader); if (err) { fprint_err("### Error closing input file %s\n", context->input_names[context->default_file_index]); return 1; } return 0; } /* * Run as a player, possibly reading commands via a socket */ static int command_reader(tsserve_context_p context, char *output_name, int port, int use_stdin, int verbose, int quiet) { int err; int ii, had_err; TS_writer_p tswriter = NULL; PES_reader_p reader[MAX_INPUT_FILES]; err = tswrite_open(TS_W_TCP,output_name,NULL,port,quiet,&tswriter); if (err) { fprint_err("### Unable to connect to %s\n",output_name); return 1; } if (context->drop_packets) { tswriter->drop_packets = context->drop_packets; tswriter->drop_number = context->drop_number; } #ifndef _WIN32 // Maybe enable command input from stdin if (use_stdin) { if (!quiet) print_msg("Commands from standard input:\n" " q = quit\n" " n = normal speed\n" " p = pause (the initial state)\n" " f = fast forward\n" " F = fast fast forward\n" " r = reverse\n" " R = fast reverse\n" " > < = skip forwards, back by 10 seconds\n" " ] [ = skip forwards, back by 3 minutes\n" " 0..9 = select file 0 through 9 (if defined),\n" " rewind it and play at normal speed\n" "Use newline to 'send' a command or sequence of commands.\n"); err= tswrite_start_input(tswriter,STDIN_FILENO); if (err) { print_err("### Unable to start command input from stdin\n"); (void) tswrite_close(tswriter,TRUE); return 1; } } else #endif // _WIN32 { err= tswrite_start_input(tswriter,tswriter->where.socket); if (err) { fprint_err("### Unable to start command input from %s\n", output_name); (void) tswrite_close(tswriter,TRUE); return 1; } } err = open_input_files(context,quiet,verbose,reader); if (err) { print_err("### Unable to open input file\n"); (void) tswrite_close(tswriter,TRUE); return 1; } // And play... err = play_pes_packets(reader,tswriter,context,verbose,quiet); if (err) { print_err("### Error playing PES packets\n"); (void) tswrite_close(tswriter,TRUE); for (ii=0;iiinput_names[ii]); had_err = TRUE; } } return (had_err?1:0); } static void print_usage() { print_msg( "Usage:\n" " tsserve \n" " tsserve -port \n" " tsserve [switches] [switches]\n" "\n" ); REPORT_VERSION("tsserve"); print_msg( "\n" " Act as a server which plays the given file (containing Transport\n" " Stream or Program Stream data). The output is always Transport\n" " Stream.\n" "\n" "Input:\n" " An H.222.0 TS or PS file to serve to the client.\n" " This will be treated as file 0 (see below).\n" "\n" " -0 .. -9 \n" " Specify files 0 through 9, selectable with command\n" " characters 0 through 9. The lowest numbered file\n" " will be the default for display.\n" "\n" "General Switches:\n" " -details Print out more detailed help information,\n" " including some less common options.\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -quiet, -q Suppress informational and warning messages.\n" " -verbose, -v Output additional diagnostic messages\n" " -port Listen for a client on port (default 88)\n" " -noaudio Ignore any audio data\n" " -pad Pad the start of the output with filler TS\n" " packets, to allow the client to synchronize with\n" " the datastream. Defaults to 8.\n" "\n" " -noseqhdr Do not output sequence headers for fast forward/reverse\n" " data. Only relevant to H.262 data.\n" "\n" "Program Stream Switches:\n" "\n" " -prepeat Output the program data (PAT/PMT) after every \n" " PS packs. Defaults to 100.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" " Both of these affect the stream type of the output data.\n" "\n" " If the audio stream being output is Dolby (AC-3), then the stream type\n" " used to output it differs for DVB (European) and ATSC (USA) data. It\n" " may be specified as follows:\n" "\n" " -dolby dvb Use stream type 0x06 (the default)\n" " -dolby atsc Use stream type 0x81\n" "\n" " For information on using the program in other modes, see -details.\n" ); } static void print_detailed_usage() { print_msg( "Usage: tsserve [switches] \n" "\n" " Copyright (c) 2004 SJ Consulting Ltd.\n" "\n" " Reads from a file containing H.222.0 (ISO/IEC 13818-1) Transport\n" " Stream or Program Stream data (converting PS to TS as it goes),\n" " and 'plays' the Transport Stream 'at' a client.\n" "\n" " Assumes a single program in the file, and for PS assumes that the\n" " program stream is well formed - i.e., that it starts with a pack\n" " header. A PS stream that ends after a PES packet, but without an\n" " MPEG_program_end_code will cause a warning message, but will not\n" " be treated as an error.\n" "\n" " In the default mode, the program acts as a server, listening for\n" " clients on port 88 (or the port specified with -port). When a\n" " client connects to the port, the program starts listening for\n" " commands from the client, and acting appropriately. When the\n" " client sends the 'q'uit command, the program disconnects from\n" " the client, and listens for another.\n" "\n" " Alternative modes may be specified with -cmd, -cmdstdin and\n" " -test.\n" "\n" "Input:\n" " An H.222.0 TS or PS file.\n" " If given before any of -0..-9, this will be treated\n" " as a specification of file 0. If given after -0..-9,\n" " it will be treated as an error.\n" "\n" " -0 .. -9 \n" " Specify files 0 through 9, selectable with command\n" " characters 0 through 9. The lowest numbered name\n" " will be selected as the default.\n" "\n" "General Switches:\n" " -details Present this text.\n" " -quiet, -q Only output error messages.\n" " -verbose, -v Output progress messages.\n" "\n" " Normal operation outputs some messages summarising the command line\n" " choices, information about data from the input file, confirmation\n" " when the program is ending, etc.\n" " Quiet operation endeavours only to output error messages.\n" " Verbose operation outputs diagnostic information, not intended for\n" " normal use.\n" "\n" " -x Output *extra* information." "\n" " The extra information output gives more details about what the\n" " server is doing in reaction to the commands given by the client.\n" " It is intended as a diagnostic aid during development.\n" "\n" " -port Listen for a client on port (default 88)\n" " Ignored if -cmd, -cmdstdin or -test is\n" " specified\n" "\n" " -noaudio Don't output audio data\n" "\n" " -pad Pad the start of the output with filler TS\n" " packets, to allow the client to synchronize with\n" " the datastream. Defaults to 8.\n" "\n" " -noseqhdr Do not output sequence headers for fast forward/reverse\n" " data. Only relevant to H.262 data.\n" "\n" "Program Stream Switches:\n" "\n" " The following switches are only applicable if the input data is PS.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" "\n" " If input is from a file, then the program will look at the start of\n" " the file to determine if the stream is H.264 or H.262 data. This\n" " process may occasionally come to the wrong conclusion, in which case\n" " the user can override the choice using the switches above.\n" "\n" " If the audio stream being output is Dolby (AC-3), then the stream type\n" " used to output it differs for DVB (European) and ATSC (USA) data. It\n" " may be specified as follows:\n" "\n" " -dolby dvb Use stream type 0x06 (the default)\n" " -dolby atsc Use stream type 0x81\n" "\n" "Transport Stream Switches:\n" "\n" " The following switches are only applicable if the input data is TS.\n" "\n" " -tsdirect In normal play, copy all TS packets to the client,\n" " instead of just sending the PES packets for the video\n" " and audio streams'\n" "\n" " Note that when -tsdirect is specified, PES packets are still inspected\n" " to allow building up the fast forward/reverse indices.\n" " Also, -prepeat, -pes_padding and -drop will have no effect with this switch.\n" "\n" "Other stuff:\n" "\n" " -prepeat Output the program data (PAT/PMT) after every \n" " PES packets, to allow a TS reader to resynchronise\n" " if it starts reading part way through the stream.\n" " PAT/PMT pairs are also output before 'significant'\n" " events (changing speed/direction/etc.).\n" " Defaults to 100.\n" "\n" " -ffreq Frequency for faster forward ('F'). Default is 8.\n" " -rfreq Frequency for reverse (fast reverse is twice\n" " the speed). Default is 8.\n" "\n" " -pes_padding When outputting in 'normal play' mode, input PES packets\n" " are copied to the output. If '-pes_padding' is used, then \n" " dummy PES packets will be added to the output for each input\n" " packet, causing the amount of data output to be roughly +1\n" " times as great. This can be useful for benchmarking the recipient.\n" "\n" " -drop As TS packets are output, for every + packets,\n" " keep and then drop (throw away) .\n" " Applies to all TS packets output, regardless of selected file.\n" " This can be useful when testing other applications.\n" "\n" "Alternate modes\n" "---------------\n" " Command input and testing modes connect directly to a host, and thus\n" " the host to use must be specified.\n" "\n" " -host [:\n" " The host to which to write TS packets, over\n" " TCP/IP. If is not specified, it defaults\n" " to 88.\n" "\n" "Command input:\n" " -cmd Enables command input, from the host.\n" " -cmdstdin Enables command input, from standard input.\n" " This is not supported on Windows.\n" "\n" " In command input mode, the program connects to the host specified\n" " with -host, and takes commands either from the host, or from\n" " standard input.\n" "\n" " Command characters are:\n" " q quit.\n" " n normal play.\n" " p pause (the startup state).\n" " f fast forward (uses 'strip').\n" " F fast fast forward (uses 'filter').\n" " r reverse.\n" " R fast reverse.\n" " > < skip forwards/back by 10 seconds.\n" " ] [ skip forwards/back by 3 minutes.\n" " 0..9 select file 0 through 9 (as defined by switches -0 to\n" " -9, see above), rewind it and play at normal speed.\n" " Any other character is ignored.\n" " Note that if command input is from standard input, a newline must\n" " be typed before command characters are 'seen', and if there are\n" " multiple characters on a line, they will be obeyed in sequence.\n" "\n" "Testing:\n" " -test Test by running a sequence of pictures at the\n" " specified host. The exact sequence used can be\n" " determined with the -f, etc., switches:\n" "\n" " -f Loop outputting pictures at normal speed,\n" " -n then fast forward past pictures, then at\n" " -ff normal speed, then at the higher fast forward\n" " -r speed, then at normal speed again, then\n" " reverse past . Repeat until stopped.\n" "\n" " If '-f 0 -ff 0 -r 0' is specified, then the data will just play at\n" " normal speed, ignoring -n.\n" "\n" " -skiptest Test forwards and backwards skipping.\n" "\n" " -output , -o \n" " If -test is being used then output may be\n" " redirected to a file, instead of a host.\n" ); } int main(int argc, char **argv) { char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; int output_port = 88; // Useful default port number int quiet = FALSE; int verbose = FALSE; int use_stdin = FALSE; // for command input... int listen_port = 88; enum ACTION action = ACTION_SERVER; int err = 0; int ii; int argno = 1; // Testing specific options int num_normal = 100; int num_fast = 100; int num_faster = 100; int num_reverse = 100; int output_to_file = FALSE; int skiptest = FALSE; struct tsserve_context context; for (ii = 0; ii < MAX_INPUT_FILES; ii++) context.input_names[ii] = NULL; context.video_only = FALSE; context.pad_start = 8; context.ffrequency = DEFAULT_FORWARD_FREQUENCY; context.rfrequency = DEFAULT_REVERSE_FREQUENCY; context.with_seq_hdrs = TRUE; context.pes_padding = 0; context.drop_packets = 0; context.drop_number = 0; // Program Stream specific options context.pmt_pid = 0x66; context.audio_pid = 0x67; context.video_pid = 0x68; context.pcr_pid = context.video_pid; // Use PCRs from the video stream context.repeat_program_every = 100; // Transport Stream specific options context.tsdirect = FALSE; // Write to server as a side effect of PES reading context.force_stream_type = FALSE; context.want_h262 = TRUE; // shouldn't matter context.dolby_is_dvb = TRUE; if (argc < 2) { print_usage(); return 0; } while (argno < argc) { if (argv[argno][0] == '-') { if (!strcmp("--help",argv[argno]) || !strcmp("-h",argv[argno]) || !strcmp("-help",argv[argno])) { print_usage(); return 0; } else if (!strcmp("-err",argv[argno])) { CHECKARG("tsserve",argno); if (!strcmp(argv[argno+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[argno+1],"stdout")) redirect_output_stdout(); else { fprint_err("### tsserve: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[argno+1]); return 1; } argno++; } else if (!strcmp("-details",argv[argno])) { print_detailed_usage(); return 0; } else if (!strcmp("-noseqhdr",argv[argno]) || !strcmp("-noseqhdrs",argv[argno])) { context.with_seq_hdrs = FALSE; } else if (!strcmp("-skiptest",argv[argno])) { action = ACTION_TEST; skiptest = TRUE; } else if (!strcmp("-test",argv[argno])) { action = ACTION_TEST; skiptest = FALSE; } else if (!strcmp("-tsdirect",argv[argno])) { context.tsdirect = TRUE; // Write to server as a side effect of TS reading } else if (!strcmp("-n",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &num_normal); if (err) return 1; argno++; } else if (!strcmp("-f",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10,&num_fast); if (err) return 1; argno++; } else if (!strcmp("-ff",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &num_faster); if (err) return 1; argno++; } else if (!strcmp("-r",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &num_reverse); if (err) return 1; argno++; } else if (!strcmp("-ffreq",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,0, &context.ffrequency); if (err) return 1; argno++; } else if (!strcmp("-rfreq",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,0, &context.rfrequency); if (err) return 1; argno++; } else if (!strcmp("-pes_padding",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &context.pes_padding); if (err) return 1; argno++; } else if (!strcmp("-drop",argv[argno])) { if (ii+2 >= argc) { print_err("### tsserve: -drop requires two arguments\n"); return 1; } err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,0, &context.drop_packets); if (err) return 1; err = int_value("tsserve",argv[argno],argv[argno+2],TRUE,0, &context.drop_number); if (err) return 1; argno += 2; } else if (!strcmp("-quiet",argv[argno]) || !strcmp("-q",argv[argno])) { quiet = TRUE; verbose = FALSE; } else if (!strcmp("-verbose",argv[argno]) || !strcmp("-v",argv[argno])) { quiet = FALSE; verbose = TRUE; } else if (!strcmp("-x",argv[argno])) { extra_info = TRUE; } else if (!strcmp("-noaudio",argv[argno])) { context.video_only = TRUE; } else if (!strcmp("-avc",argv[argno]) || !strcmp("-h264",argv[argno])) { context.force_stream_type = TRUE; context.want_h262 = FALSE; } else if (!strcmp("-h262",argv[argno])) { context.force_stream_type = TRUE; context.want_h262 = TRUE; } else if (!strcmp("-dolby",argv[argno])) { CHECKARG("tsserve",argno); if (!strcmp("dvb",argv[argno+1])) context.dolby_is_dvb = TRUE; else if (!strcmp("atsc",argv[argno+1])) context.dolby_is_dvb = FALSE; else { print_err("### tsserve: -dolby must be followed by dvb or atsc\n"); return 1; } ii++; } else if (!strcmp("-prepeat",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &context.repeat_program_every); if (err) return 1; argno++; } else if (!strcmp("-pad",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &context.pad_start); if (err) return 1; argno++; } else if (!strcmp("-port",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,0, &listen_port); if (err) return 1; argno++; } else if (!strcmp("-cmd",argv[argno])) { action = ACTION_CMD; } else if (!strcmp("-cmdstdin",argv[argno])) { use_stdin = TRUE; action = ACTION_CMD; } else if (!strcmp("-host",argv[argno])) { CHECKARG("tsserve",argno); err = host_value("tsserve",argv[argno],argv[argno+1], &output_name,&output_port); if (err) return 1; had_output_name = TRUE; // more or less argno++; } else if (!strcmp("-0",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[0] = argv[argno+1]; argno++; } else if (!strcmp("-1",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[1] = argv[argno+1]; argno++; } else if (!strcmp("-2",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[2] = argv[argno+1]; argno++; } else if (!strcmp("-3",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[3] = argv[argno+1]; argno++; } else if (!strcmp("-4",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[4] = argv[argno+1]; argno++; } else if (!strcmp("-5",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[5] = argv[argno+1]; argno++; } else if (!strcmp("-6",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[6] = argv[argno+1]; argno++; } else if (!strcmp("-7",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[7] = argv[argno+1]; argno++; } else if (!strcmp("-8",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[8] = argv[argno+1]; argno++; } else if (!strcmp("-9",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[9] = argv[argno+1]; argno++; } else if (!strcmp("-output",argv[argno]) || !strcmp("-o",argv[argno])) { CHECKARG("tsserve",argno); output_to_file = TRUE; had_output_name = TRUE; output_name = argv[argno+1]; argno++; } else { fprint_err("### tsserve: " "Unrecognised command line switch '%s'\n",argv[argno]); return 1; } } else { if (had_input_name) { fprint_err("### tsserve: Unexpected '%s'\n",argv[argno]); return 1; } else { context.input_names[0] = argv[argno]; had_input_name = TRUE; } } argno++; } if (!had_input_name) { print_err("### tsserve: No input file specified\n"); return 1; } if (!had_output_name && action != ACTION_SERVER) { print_err("### tsserve: No output specified\n"); return 1; } if (output_to_file && action != ACTION_TEST) { print_err("### tsserve: Output to a file (-output) is only allowed" " with -test\n"); return 1; } if (!quiet) { print_msg("Input files:\n"); for (ii = 0; ii < MAX_INPUT_FILES; ii++) { if (context.input_names[ii] != NULL) fprint_msg(" %2d: %s\n",ii,context.input_names[ii]); } } for (ii = 0; ii < MAX_INPUT_FILES; ii++) { if (context.input_names[ii] != NULL) { context.default_file_index = ii; if (!quiet) fprint_msg("File %d (%s) selected as default\n", ii,context.input_names[ii]); break; } } if (context.tsdirect && !quiet) print_msg("Serving all TS packets, not just video/audio streams\n"); if (context.drop_packets && !quiet) fprint_msg("DROPPING: Keeping %d TS packet%s, then dropping (throwing away) %d\n", context.drop_packets,(context.drop_packets==1?"":"s"), context.drop_number); switch (action) { case ACTION_SERVER: err = run_server(&context,listen_port,verbose,quiet); if (err) { print_err("### tsserve: Error in server\n"); return 1; } break; case ACTION_TEST: err = test_reader(&context,output_to_file,output_name,output_port, num_normal,num_fast,num_faster,num_reverse,skiptest, verbose,quiet,context.tsdirect); if (err) { fprint_err("### tsserve: Error playing to %s\n",output_name); return 1; } break; case ACTION_CMD: err = command_reader(&context,output_name,output_port, use_stdin,verbose,quiet); if (err) { fprint_err("### tsserve: Error playing to %s\n",output_name); return 1; } break; default: print_err("### No action specified\n"); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tswrite.c000066400000000000000000003412531261471605300165450ustar00rootroot00000000000000/* * Support for writing out TS packets, to file, or over TCP/IP or UDP * * When writing asynchronously, provides automated producer/consumer * behaviour via a circular buffer, optionally taking timing from the * TS PCR entries. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #include // for isprint #include #include // Sleeping and timing #ifdef _WIN32 #include #include #include #else // _WIN32 #include #include // gettimeofday #include // memory mapping #include #include // send #endif // _WIN32 #include "compat.h" #include "misc_fns.h" #include "printing_fns.h" #include "tswrite_fns.h" #include "ts_fns.h" // ------------------------------------------------------------ // Global flags affecting debugging #define DEBUG_DATA_WAIT 0 // If the user is giving commands to tell the process what to do // (as read in read_comand), do we want to report on each command // character as it is read? #define DEBUG_COMMANDS 0 // Do we want to be able to display the circular buffer contents? #define DISPLAY_BUFFER 1 #if DISPLAY_BUFFER static int global_show_circular = FALSE; #endif static int global_parent_debug = FALSE; static int global_show_all_times = FALSE; // extra info for the above static int global_child_debug = FALSE; // Should we try to simulate network choppiness by randomly perturbing // the child process's idea of time? If `global_perturb_range` is non-zero, // then yes, we should (admittedly with rather a blunt hammer). // In which case, we then specify a seed to use for our random perturbations, // and a range for the time in milliseconds that we should use as a range // (we'll generate values for that range on either side of zero). static unsigned global_perturb_seed; static unsigned global_perturb_range = 0; static int global_perturb_verbose = FALSE; // ------------------------------------------------------------ // The default number of set-of-N-packets to allow for in priming the // output buffers #define DEFAULT_PRIME_SIZE 10 // A millisecond is a useful unit for waiting, but nanosleep works // in nanoseconds, so let's define one in terms of the other // (nanosecond == 10e-9s, microsecond = 10e-6s, millisecond = 10e-3s) #define ONE_MS_AS_NANOSECONDS 1000000 // Default waits // The parent can afford to wait longer than the child // 10ms seems reasonable as a default for the child #define DEFAULT_PARENT_WAIT 50 #define DEFAULT_CHILD_WAIT 10 // We need some guess at an initial data rate, if the user does not give us one // (note that this is in bytes per second) #define DEFAULT_BYTE_RATE 250000 // I'm happy to have these affect everyone using this "module", // at least for the moment - changing the values is likely to be only // for experimentation, and then the default values will be settled // appropriately // // Wait times for parent and child, in milliseconds static int global_parent_wait = DEFAULT_PARENT_WAIT; static int global_child_wait = DEFAULT_CHILD_WAIT; // If the child waits for a very long time, it may (is allowed to) assume that // the parent has stopped feeding it. We need a number of times it should try // waiting its global_child_wait before it decides to give up (so we may // assume it has waited, in total, at least this number times the length of // time it waits each time). #define CHILD_GIVE_UP_AFTER 1000 // And a similar quantity for the parent, in case the child dies #define PARENT_GIVE_UP_AFTER 1000 // If not being quiet, report progress every REPORT_EVERY packets read #define REPORT_EVERY 10000 // ============================================================ // CIRCULAR BUFFER // ============================================================ // We default to using a "packet" of 7 transport stream packets because 7*188 = // 1316, but 8*188 = 1504, and we would like to output as much data as we can // that is guaranteed to fit into a single ethernet packet, size 1500. #define DEFAULT_TS_PACKETS_IN_ITEM 7 // For simplicity, we'll have a maximum on that (it allows us to have static // array sizes in some places). This should be a big enough size to more than // fill a jumbo packet on a gigabit network. #define MAX_TS_PACKETS_IN_ITEM 100 // ------------------------------------------------------------ // A circular buffer, usable as a queue // // We "waste" one buffer item so that we don't have to maintain a count // of items in the buffer // // To get an understanding of how it works, choose a small BUFFER_SIZE // (e.g., 11), enable DISPLAY_BUFFER, and select --visual - this will show the // reading/writing of the circular buffer in action, including the // "unused item". // // The data for the circular buffer // Each circular buffer item "contains" (up to) N TS packets (where N defaults // to 7, and is specified as `item_size` in the circular buffer header), and a // time (in microseconds) when we would like it to be output (relative to the // time for the first packet "sent"). // // Said data is stored at the address indicated by the circular buffer // "header", as `item_data`. // struct circular_buffer_item { uint32_t time; // when we would like this data output int discontinuity; // TRUE if our timeline has "broken" int length; // number of bytes of data in the array }; typedef struct circular_buffer_item *circular_buffer_item_p; #define SIZEOF_CIRCULAR_BUFFER_ITEM sizeof(struct circular_buffer_item) typedef struct rtp_hdr_info_s { uint16_t seq; uint32_t ssrc; } rtp_hdr_info_t; // ------------------------------------------------------------ // The header for the circular buffer // // Note that `start` is only ever written to by the child process, and this is // the only thing that the child process ever changes in the circular buffer. // // `maxnowait` is the maximum number of packets to send to the target host // without forcing an intermediate wait - required to stop us "swamping" the // target with too much data, and overrunning its buffers. struct circular_buffer { volatile int start; // start of data "pointer" volatile int end; // end of completed data "pointer" (you guessed) volatile int pending; // end of buffered but not ready for xmit int size; // the actual length of the `item` array volatile int eos; // end of stream int TS_in_item; // max number of TS packets in a circular buffer item int item_size; // and thus the size of said item's data array int hdr_size; tswrite_pkt_hdr_type_t hdr_type; union { rtp_hdr_info_t rtp; } hdr; int maxnowait; // max number consecutive packets to send with no wait int waitfor; // the number of microseconds to wait thereafter // The location of the packet data for the circular buffer items byte *item_data; // The "header" data for each circular buffer item struct circular_buffer_item item[]; }; typedef struct circular_buffer *circular_buffer_p; // Note that size doesn't include the final `item` #define SIZEOF_CIRCULAR_BUFFER sizeof(struct circular_buffer) // For PCR2 pacing we accumulate the initial packets here so it must be big // enough to cope with a max bitrate stream // Say 100Mbits is the fastest we are going to care about // So we need to buffer 2 PCRs which have a max spacing of .1s (by the std) // and another .1s before that for having just missed a PCR = .2s = 20Mbits // = 2.5Mbytes. 2048 * 188 * 7 = 2.7M #define DEFAULT_CIRCULAR_BUFFER_SIZE 2048 // used to be 100 #define PRIME_SPEED_NORMAL 100 // ============================================================ // BUFFERED OUTPUT // ============================================================ // Information about each TS packet in our circular buffer item struct TS_packet_info { int index; uint32_t pid; // do we need the PIDs? int got_pcr; uint64_t pcr; }; typedef struct TS_packet_info *TS_packet_info_p; #define SIZEOF_TS_PACKET_INFO sizeof(struct TS_packet_info); // PCR interpolation structure typedef struct pcr_pace_env_s { uint32_t gap_bytes; uint32_t next_bytes; int32_t next_offset; int next_index; int pcr1_set; // Seen 1st PCR int gap_set; // Seen 2nd PCR int pkt1; // 1st pkt dealt with? (!discontinuity) uint64_t pcr1; uint64_t pcr_base; int prime_req; int prime_speed; uint64_t prime_last_pcr; uint32_t prev_gap_bytes; uint64_t prev_pcr_gap; uint64_t next_pcr_base; } pcr_pace_env; // If we're going to support output via our circular buffer in a manner // similar to that for output to a file or socket, then we need a structure // to maintain the relevant information. It seems a bit wasteful to burden // the circular buffer itself with this, particularly as only the writer // cares about this data, so it needn't be shared. struct buffered_TS_output { circular_buffer_p buffer; int which; // Which buffer index we're writing to int started; // TRUE if we've started writing therein // For each TS packet in the circular buffer, remember its `count` // within the input stream, whether it had a PCR, and if so what that // PCR was. To make it simpler to access these arrays, also keep a fill // index into them (the alternative would be to always re-zero the // `got_pcr` values whenever we start a new circular buffer entry, // which would be a pain...) int num_packets; // how many TS packets we've got struct TS_packet_info packet[MAX_TS_PACKETS_IN_ITEM]; // `rate` is the rate (in bytes per second) we would like to output data at uint32_t rate; // `pcr_scale` is a multiplier for PCRs - each PCR found gets its value // multiplied by this double pcr_scale; // `use_pcrs` indicates if we should use PCRs in the data to drive our // timing, rather than use the specified byte rate directly. The `priming` // values are only relevant if `use_pcrs` is true. tswrite_pcr_mode pcr_mode; // 'prime_size' is the amount of space/time to 'prime' the circular buffer // output timing mechanism with. This is effectively multiples of the // size of a circular buffer item. int prime_size; // Percentage "too fast" speedup for our priming rate int prime_speedup; pcr_pace_env pcr_pace; }; #ifdef _WIN32 // ============================================================ // Windows specific - gettimeofday replacement // ============================================================ /* * Windows does not provide gettimeofday, but it has equivalent functionality, * and does provide timeval, so wae can pretend... */ static inline void gettimeofday(struct timeval *tv, void *timezone) { struct _timeb timebuffer; _ftime(&timebuffer); tv->tv_sec = (long)timebuffer.time; tv->tv_usec = timebuffer.millitm * 1000; } #endif // ============================================================ // Low level circular buffer support // ============================================================ /* * Set up our circular buffer in shared memory * * - `buf` is a pointer to the new shared memory * - `circ_buf_size` is the number of buffer entries (plus one) we would * like. * - `TS_in_packet` is the number of TS packets to allow in each network * packet/circular buffer item. * - `maxnowait` is the maximum number of packets to send to the target * host with no wait between packets * - `waitfor` is the number of microseconds to wait for thereafter * * Returns 0 if all goes well, 1 if something goes wrong. */ static int map_circular_buffer(circular_buffer_p *circular, int circ_buf_size, int TS_in_packet, int maxnowait, int waitfor, const tswrite_pkt_hdr_type_t hdr_type) { // Rather than map a file, we'll map anonymous memory // BSD supports the MAP_ANON flag as is, // Linux (bless it) deprecates MAP_ANON and would prefer us to use // the more verbose MAP_ANONYMOUS (but MAP_ANON is still around, so // we'll stick with that while we can) // The shared memory starts with the circular buffer "header". This ends with // an array of `circular_buffer_item` structures, of length `circ_buf_size`. // // Each circular buffer item needs enough space to store (up to) // `TS_in_packet` TS entries (so, `TS_in_packet`*188 bytes). Since that size // is not fixed, we can't just allocate it "inside" the buffer items (it // wouldn't be nice to allocate the *maximum* possible space we might want!). // Instead, we'll put it as a byte array after the rest of our data. // // Space may be left to add an RTP header before each items data // // So: const int hdr_size = (hdr_type == PKT_HDR_TYPE_RTP) ? 12 : 0; int base_size = SIZEOF_CIRCULAR_BUFFER + (circ_buf_size * SIZEOF_CIRCULAR_BUFFER_ITEM); int data_size = circ_buf_size * (TS_in_packet * TS_PACKET_SIZE + hdr_size); int total_size = base_size + data_size; circular_buffer_p cb; *circular = NULL; #ifdef _WIN32 // Under Windows, we're using threading to manage our parent/child // processes, so we can just use malloc here cb = malloc(total_size); if (cb == NULL) { fprint_err("### Error mapping circular buffer as shared memory: %s\n", strerror(errno)); return 1; } #else // _WIN32 cb = mmap(NULL,total_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); if (cb == MAP_FAILED) { fprint_err("### Error mapping circular buffer as shared memory: %s\n", strerror(errno)); return 1; } #endif // _WIN32 cb->start = 1; cb->end = 0; cb->pending = 0; cb->eos = FALSE; cb->size = circ_buf_size; cb->TS_in_item = TS_in_packet; cb->item_size = TS_in_packet * TS_PACKET_SIZE + hdr_size; cb->hdr_size = hdr_size; cb->hdr_type = hdr_type; if (hdr_type == PKT_HDR_TYPE_RTP) { struct timeval now; gettimeofday(&now, NULL); cb->hdr.rtp.seq = 0; cb->hdr.rtp.ssrc = (uint32_t)(now.tv_sec ^ now.tv_usec << 12); // A somewhat random number } cb->maxnowait = maxnowait; cb->waitfor = waitfor; cb->item_data = (byte *) cb + base_size + hdr_size; *circular = cb; return 0; } /* * Release the shared memory containing our circular buffer * * - `buf` is a pointer to the shared memory * * Returns 0 if all goes well, 1 if something goes wrong. */ static int unmap_circular_buffer(circular_buffer_p circular) { int base_size = SIZEOF_CIRCULAR_BUFFER + (circular->size * SIZEOF_CIRCULAR_BUFFER_ITEM); int data_size = circular->size * circular->item_size; int total_size = base_size + data_size; #ifdef _WIN32 // Under Windows, we're using threading to manage our parent/child // processes, so we malloced our circular buffer free(circular); #else // _WIN32 int err = munmap(circular,total_size); if (err) { fprint_err("### Error unmapping circular buffer from shared memory: %s\n", strerror(errno)); return 1; } #endif // _WIN32 return 0; } /* * Is the buffer empty? */ static inline int circular_buffer_empty(circular_buffer_p circular) { return (circular->start == (circular->end + 1) % circular->size); } /* * Is the buffer full? */ static inline int circular_buffer_full(circular_buffer_p circular) { return ((circular->pending + 2) % circular->size == circular->start); } // Is the buffer full and never going to empty? static inline int circular_buffer_jammed(circular_buffer_p circular) { return ((circular->pending + 1) % circular->size == circular->end); } /* * If the circular buffer is empty, wait until it gains some data. * * Returns 0 if all goes well, 1 if something goes wrong. */ static inline int wait_if_buffer_empty(circular_buffer_p circular) { static int count = 0; #ifndef _WIN32 struct timespec time = {0,global_child_wait*ONE_MS_AS_NANOSECONDS}; int err; #endif // _WIN32 while (circular_buffer_empty(circular) && !circular->eos) { #if DISPLAY_BUFFER if (global_show_circular && !global_parent_debug) print_msg("<-- wait\n"); #endif if (global_parent_debug) print_msg("<-- wait\n"); count ++; #ifdef _WIN32 Sleep(global_child_wait); #else // _WIN32 err = nanosleep(&time,NULL); if (err == -1 && errno == EINVAL) { fprint_err("### Child: bad value (%ld) for wait time\n",time.tv_nsec); return 1; } #endif // _WIN32 // If we wait for a *very* long time, maybe our parent has crashed if (count > CHILD_GIVE_UP_AFTER) { print_err("### Child: giving up (parent not responding)\n"); return 1; } } count = 0; return circular_buffer_empty(circular); // If empty then EOS so return 1 } /* * Wait for the circular buffer to fill up * * Returns 0 if all goes well, 1 if something goes wrong. */ static inline int wait_for_buffer_to_fill(circular_buffer_p circular) { static int count = 0; #ifndef _WIN32 struct timespec time = {0,global_child_wait*ONE_MS_AS_NANOSECONDS}; int err; #endif // _WIN32 while (!circular_buffer_full(circular) && !circular->eos) { #if DISPLAY_BUFFER if (global_show_circular && !global_child_debug) print_msg("<-- wait for buffer to fill\n"); #endif if (global_child_debug) print_msg("<-- wait for buffer to fill\n"); count ++; #ifdef _WIN32 Sleep(global_child_wait); #else // _WIN32 err = nanosleep(&time,NULL); if (err == -1 && errno == EINVAL) { fprint_err("### Child: bad value (%ld) for wait time\n",time.tv_nsec); return 1; } #endif // _WIN32 // If we wait for a *very* long time, maybe our parent has crashed if (count > CHILD_GIVE_UP_AFTER) { print_err("### Child: giving up (parent not responding)\n"); return 1; } } count = 0; return 0; } /* * If the circular buffer is full, wait until it gains some room. * * Returns 0 if all goes well, 1 if something goes wrong. */ static inline int wait_if_buffer_full(circular_buffer_p circular) { static int count = 0; #ifndef _WIN32 struct timespec time = {0,global_parent_wait*ONE_MS_AS_NANOSECONDS}; int err; #endif // _WIN32 while (circular_buffer_full(circular)) { #if DISPLAY_BUFFER if (global_show_circular && !global_parent_debug) print_msg("--> wait\n"); #endif if (global_parent_debug) print_msg("--> wait\n"); count ++; #ifdef _WIN32 Sleep(global_parent_wait); #else // _WIN32 err = nanosleep(&time,NULL); if (err == -1 && errno == EINVAL) { fprint_err("### Parent: bad value (%ld) for wait time\n",time.tv_nsec); return 1; } #endif // _WIN32 if (circular_buffer_jammed(circular)) { print_err("### Circular buffer jammed: No PCRs found\n"); circular->eos = TRUE; return 1; } // If we wait for a *very* long time, maybe our child has crashed if (count > PARENT_GIVE_UP_AFTER) { print_err("### Parent: giving up (child not responding)\n"); return 1; } } count = 0; return 0; } /* * Print out the buffer contents, prefixed by a prefix string */ static void print_circular_buffer(char *prefix, circular_buffer_p circular) { int ii; if (prefix != NULL) fprint_msg("%s ",prefix); for (ii = 0; ii < circular->size; ii++) { byte* offset = circular->item_data + (ii * circular->item_size); fprint_msg("%s",(circular->start == ii ? "[":" ")); if (*offset == 0) print_msg(".."); else fprint_msg("%02x",*offset); fprint_msg("%s ",(circular->end == ii ? "]":" ")); } print_msg("\n"); } // ============================================================ // Low level buffered TS output support // ============================================================ static void reset_pcr_time(pcr_pace_env * const ppe, const uint64_t next_pcr_base) { memset(ppe, 0, sizeof(*ppe)); ppe->pcr_base = next_pcr_base; ppe->prime_speed = PRIME_SPEED_NORMAL; ppe->prime_last_pcr = INT64_MIN; } /* * Build a buffered output context * * - `writer` is the new buffered output context * - `circ_buf_size` is the number of buffer entries (plus one) we would * like in the underlying circular buffer. * - `TS_in_packet` is the number of TS packets to allow in each network * packet/circular buffer item. * - `maxnowait` is the maximum number of packets to send to the target * host with no wait between packets * - `waitfor` is the number of microseconds to wait for thereafter * - `rate` is the (initial) rate at which we'd like to output our data * - `use_pcrs` is TRUE if PCRs in the data stream are to be used for * timing output (the normal case), otherwise the specified byte rate * will be used directly. * - `prime_size` is how much to prime the circular buffer output timer * - `prime_speedup` is the percentage of "normal speed" to use for the priming * rate. This should normally be set to 100 (i.e., no effect). * - `pcr_scale` indicates how much PCRs should be "inflated" * * Returns 0 if all went well, 1 if something went wrong. */ static int build_buffered_TS_output(buffered_TS_output_p *writer, int circ_buf_size, int TS_in_packet, int maxnowait, int waitfor, int rate, tswrite_pcr_mode pcr_mode, int prime_size, int prime_speedup, double pcr_scale, const tswrite_pkt_hdr_type_t hdr_type) { int err, ii; circular_buffer_p circular; buffered_TS_output_p new = calloc(1, SIZEOF_BUFFERED_TS_OUTPUT); if (new == NULL) { print_err("### Unable to allocate buffered output\n"); return 1; } reset_pcr_time(&new->pcr_pace, 0); err = map_circular_buffer(&circular,circ_buf_size,TS_in_packet, maxnowait,waitfor,hdr_type); if (err) { print_err("### Error building buffered output\n"); free(new); return 1; } new->buffer = circular; new->started = FALSE; new->which = (circular->pending + 1) % circular->size; new->num_packets = 0; new->rate = rate; new->pcr_mode = pcr_mode; new->prime_size = prime_size; new->prime_speedup = prime_speedup; new->pcr_scale = pcr_scale; new->pcr_pace.prime_speed = prime_speedup; new->pcr_pace.prime_req = (prime_speedup != PRIME_SPEED_NORMAL); fprint_msg("prime speed set to %d\n", prime_speedup); // And make sure we're absolutely safe against finding "false" PCR // values when we output the first few items... for (ii = 0; ii < MAX_TS_PACKETS_IN_ITEM; ii++) new->packet[ii].got_pcr = FALSE; *writer = new; return 0; } /* * Free a buffered output context * * `writer` is cleared and freed, and returned as NULL. The internal * circular buffer is unmapped. * * Returns 0 if all went well, 1 if something went wrong. */ static int free_buffered_TS_output(buffered_TS_output_p *writer) { if ((*writer)->buffer != NULL) { int err = unmap_circular_buffer((*writer)->buffer); if (err) { print_err("### Error freeing buffered output\n"); return 1; } } (*writer)->buffer = NULL; (*writer)->started = FALSE; free(*writer); *writer = NULL; return 0; } // Get a useful unsigned diff between two PCRs allowing for wrap through zero #define PCR_WRAP (0x200000000LL * 300LL) #define PCR_MS(n) ((int64_t)(n) * 90LL * 300LL) // 27MHz units static inline uint64_t pcr_delta_u(const uint64_t a, const uint64_t b) { return a < b ? a + PCR_WRAP - b : a - b; } // ============================================================ // Timing // ============================================================ /* * Set the time indicator for the next circular buffer item, using PCRs * * - `writer` is our buffered output context * * Returns 0 if all goes well, 1 if something goes wrong. */ static int set_buffer_item_time_pcr1(buffered_TS_output_p writer) { int ii; circular_buffer_p circular = writer->buffer; static int32_t available_bytes = 0; static double available_time = 0; static int last_pcr_index = -1; static uint64_t last_pcr; static double pcr_rate = 0; static uint32_t last_timestamp_near_PCR = 0; static uint32_t last_timestamp = 0; static int had_first_pcr = FALSE; // Did we *have* a previous PCR? static int had_second_pcr = FALSE; // And the second PCR is special, too // Remember our initial "priming" so we can replace it with a better // estimate later on static double initial_prime_time = 0; static int32_t initial_prime_bytes = 0; // Some simple statistics static int64_t total_available_bytes = 0; static double total_available_time = 0.0; static int num_availables = 0; int found_pcr = FALSE; int num_bytes; double num_microseconds; uint32_t timestamp; // A silly rate just means we haven't started yet... if (pcr_rate < 1.0) pcr_rate = writer->rate; // We start off with our time/bytes available zero to trigger this. // Thereafter, they should only really become zero/negative if we don't find // any PCRs. // Note that the greater `prime_size` is, the longer we can go between // PCRs, and the more smoothing effect we will have on the difference // in rates indicated by adjacent PCRs if (available_bytes <= 0 || available_time <= 0) { // We need to seed our time and data counts available_bytes = TS_PACKET_SIZE * circular->TS_in_item * writer->prime_size; available_time = available_bytes * 1000000.0 / (pcr_rate * writer->prime_speedup/100.0); if (global_parent_debug) fprint_msg("PRIMING: bytes available %6d, time available %8.1f" " (using rate %.1f x %d%%)\n", available_bytes,available_time,pcr_rate,writer->prime_speedup); if (!had_second_pcr) { initial_prime_time = available_time; initial_prime_bytes = available_bytes; } } // Have we got a PCR in our set-of-N packets? // For the moment, we're going to ignore the case where we have more than // one PCR in our set-of-N packets - it should be quite rare to have two // packets with PCRs that close together, and hopefully if we *do* get // such an instance, our compensation mechanisms will work it out. for (ii=0; iinum_packets; ii++) { if (writer->packet[ii].got_pcr) { found_pcr = TRUE; break; } } // Output our bytes using the prevailing conditions num_bytes = TS_PACKET_SIZE*writer->num_packets; num_microseconds = ((double)num_bytes / available_bytes) * available_time; timestamp = (uint32_t) (last_timestamp + num_microseconds); available_bytes -= num_bytes; available_time -= num_microseconds; if (global_parent_debug && global_show_all_times) fprint_msg("%06d: num bytes %6d, time %8.1f, timestamp %8d" " => available bytes %6d, time %8.1f\n", writer->packet[0].index,num_bytes,num_microseconds,timestamp, available_bytes,available_time); if (found_pcr) { uint64_t delta_pcr = pcr_delta_u(writer->packet[ii].pcr, last_pcr); if (delta_pcr > PCR_MS(2000)) { // We've suffered a discontinuity (quite likely because we've looped // back to the start of the file). We plainly don't want to continue // using previous PCRs as our basis for calculation, so let's fake // starting again... had_first_pcr = FALSE; had_second_pcr = FALSE; // And since we don't know what "time" is it, we'd better force // repriming next time round available_bytes = 0; available_time = 0.0; } else if (!had_first_pcr) { // This is our first PCR, so we can't do much with it except remember it had_first_pcr = TRUE; if (global_parent_debug) fprint_msg("%06d+%d: PCR %10" LLU_FORMAT_STUMP "\n", writer->packet[0].index,ii,writer->packet[ii].pcr); } else { // This is our second or later PCR - we can calculate interesting things int delta_bytes = (writer->packet[ii].index-last_pcr_index)*TS_PACKET_SIZE; int extra_bytes; double extra_time; pcr_rate = (delta_bytes * 27.0 / delta_pcr) * 1000000; extra_bytes = delta_bytes; extra_time = extra_bytes * 1000000.0 / pcr_rate; available_bytes += extra_bytes; available_time += extra_time; total_available_bytes += available_bytes; total_available_time += available_time; num_availables ++; if (global_parent_debug) { fprint_msg("%06d+%d: PCR %10" LLU_FORMAT_STUMP ", rate %9.1f, add %6d/%8.1f " " => available bytes %6d, time %8.1f\n", writer->packet[0].index,ii,writer->packet[ii].pcr,pcr_rate, extra_bytes,extra_time, available_bytes,available_time); fprint_msg(" (approximate actual rate %9.1f," " mean available bytes %8.1f, time %8.1f)\n", 1000000.0 * delta_bytes / (timestamp - last_timestamp_near_PCR), (double)total_available_bytes/num_availables, total_available_time/num_availables); } if (!had_second_pcr) // i.e., *this* is the second PCR { double old_time = available_time; // Our initial priming of the available bytes/time was based on // a guessed-at rate. However, now we have a real data rate, so // we can "remove" the original priming, and substitute one based // on this new rate (which will hopefully smooth out better) available_time -= initial_prime_time; available_time += initial_prime_bytes * 1000000.0 / pcr_rate; if (global_parent_debug) fprint_msg("RE-PRIMING: bytes available %6d, time available %8.1f" " (was %8.1f) (using rate %.1f x %d%%)\n", available_bytes,available_time,old_time,pcr_rate, writer->prime_speedup); total_available_bytes = 0; total_available_time = 0.0; num_availables = 0; // And we mustn't do this again had_second_pcr = TRUE; } } last_timestamp_near_PCR = timestamp; last_pcr = writer->packet[ii].pcr; last_pcr_index = writer->packet[ii].index; } last_timestamp = circular->item[writer->which].time = timestamp; return writer->which; } static inline void set_32_be(uint8_t * const p, const uint32_t x) { p[0] = x >> 24; p[1] = (x >> 16) & 0xff; p[2] = (x >> 8) & 0xff; p[3] = x & 0xff; } static inline void set_16_be(uint8_t * const p, const unsigned int x) { p[0] = (x >> 8) & 0xff; p[1] = x & 0xff; } // Set times on all packets between where we were and where we are now // Sets the time on both the first & last packets // Returns the index of the last circ buffer entry modified static int set_circ_times(const circular_buffer_p circ, const uint32_t index_start, const uint32_t len_bytes, const int32_t pcr1_byte_offset, const uint64_t pcr1, const uint64_t pcr_gap, const uint32_t gap_bytes, const int64_t prime_last_pcr, const int prime_speed, uint64_t * const pNew_pcr_base) { int32_t offset = pcr1_byte_offset; const int32_t end_offset = offset + len_bytes; int32_t i = index_start; int idx; do { struct circular_buffer_item * const item = circ->item + i; int64_t pcr = (int64_t)pcr1 + (int64_t)offset * (int64_t)pcr_gap / (int64_t)gap_bytes; int64_t adj_pcr = (pcr >= prime_last_pcr) ? pcr : prime_last_pcr - ((prime_last_pcr - pcr) * (int64_t)100) / (int64_t)prime_speed; if (circ->hdr_type == PKT_HDR_TYPE_RTP) { uint32_t timestamp = (uint32_t)(pcr / (uint64_t)300); uint8_t * rtp_buf = circ->item_data + i * circ->item_size - 12; rtp_buf[0] = 0x80; rtp_buf[1] = 33; // TS set_16_be(rtp_buf + 2, ++circ->hdr.rtp.seq); set_32_be(rtp_buf + 4, timestamp); set_32_be(rtp_buf + 8, circ->hdr.rtp.ssrc); } item->time = (uint32_t)(adj_pcr / 27); // "time" in us offset += item->length; idx = i; if (++i >= circ->size) i = 0; } while (offset <= end_offset); // Predict PCR at the end of this packet if wanted if (pNew_pcr_base != NULL) { *pNew_pcr_base = (int64_t)pcr1 + (int64_t)offset * (int64_t)pcr_gap / (int64_t)gap_bytes; } // fprint_msg("s: %d->%d\n", index_start, idx); return idx; } static int finalize_pcr_time(buffered_TS_output_p writer, pcr_pace_env * const ppe) { const circular_buffer_p circ = writer->buffer; int idx = -1; // fprint_msg("%s\n", __func__); if (!ppe->gap_set) { // Can't do anything - forget any pcr we may have had - but // leave accumulated bytes to be output in the prologue of any subsequent // segment ppe->pcr1_set = FALSE; } else { if (ppe->next_bytes != 0) { idx = set_circ_times(circ, ppe->next_index, ppe->next_bytes - 1, ppe->next_offset, ppe->pcr1 + ppe->pcr_base, ppe->prev_pcr_gap, ppe->prev_gap_bytes, ppe->prime_last_pcr, ppe->prime_speed, &ppe->next_pcr_base); // fprint_msg("%s: idx %d->%d\n", __func__, ppe->next_index, idx); } reset_pcr_time(ppe, ppe->next_pcr_base); } return idx; } static int discontinuity_pkt_pcr_time(buffered_TS_output_p writer, pcr_pace_env * const ppe) { return finalize_pcr_time(writer, ppe); } static int add_pkt_pcr_time(buffered_TS_output_p writer, pcr_pace_env * const ppe) { const circular_buffer_p circ = writer->buffer; const circular_buffer_item_p item = circ->item + writer->which; const TS_packet_info_p pkt0 = writer->packet + 0; int idx = -1; item->discontinuity = FALSE; retry: // Mark 1st packet after reset as discontinuity if (!ppe->pkt1) { ppe->pkt1 = TRUE; ppe->next_index = writer->which; } // If we have a pcr then we expect it to be on the 1st pkt in this group if (!pkt0->got_pcr) { ppe->gap_bytes += item->length; ppe->next_bytes += item->length; // fprint_msg("%u/%u\n", ppe->gap_bytes, ppe->next_bytes); } else { const uint64_t pcr1 = ppe->pcr1; const uint64_t pcr2 = pkt0->pcr; // fprint_msg("pcr: %lld\n", pcr2); if (!ppe->pcr1_set) { ppe->next_offset = 0 - ppe->next_bytes; ppe->next_bytes += item->length; ppe->pcr_base -= pcr2; // next_index set by discontinuity spotter if (ppe->prime_req) { // ** Really should account for bytes before 1st pcr ppe->prime_last_pcr = 27000000 * 5; ppe->prime_req = FALSE; } ppe->pcr1_set = TRUE; } else { const uint64_t pcr_gap = pcr_delta_u(pcr2, pcr1); if (pcr_gap > PCR_MS(2000)) { // Discontinuity fprint_msg("PCR2: Discontinuity[%d]: gap=%lld\n", writer->which, pcr_gap); idx = finalize_pcr_time(writer, ppe); goto retry; } idx = set_circ_times(circ, ppe->next_index, ppe->next_bytes, ppe->next_offset, ppe->pcr_base + pcr1, pcr_gap, ppe->gap_bytes, ppe->prime_last_pcr, ppe->prime_speed, &ppe->next_pcr_base); // fprint_msg("%s: idx %d->%d (%d)\n", __func__, ppe->next_index, idx, writer->which); ppe->next_offset = item->length; ppe->next_bytes = 0; ppe->next_index = writer->which + 1; if (ppe->next_index >= circ->size) ppe->next_index = 0; // Remember in case we have to predict the next segment from this one ppe->prev_pcr_gap = pcr_gap; ppe->prev_gap_bytes = ppe->gap_bytes; ppe->gap_set = TRUE; // If non-discontinuity wrap then add wrap value to base time if (pcr1 > pcr2) ppe->pcr_base += PCR_WRAP; } ppe->pcr1 = pcr2; ppe->gap_bytes = item->length; } return idx; } /* * Set the time indicator for the next circular buffer item, based solely * on the rate selected by the user * * - `writer` is our buffered output context * * Returns 0 if all goes well, 1 if something goes wrong. */ static int set_buffer_item_time_plain(buffered_TS_output_p writer) { static uint32_t last_time = 0; // The last circular buffer time stamp circular_buffer_p circular = writer->buffer; int num_bytes = writer->num_packets * TS_PACKET_SIZE;// Bytes since last time uint32_t elapsed_time = (uint32_t) (num_bytes * 1000000.0 / writer->rate); last_time += elapsed_time; circular->item[writer->which].time = last_time; return writer->which; } /* * Set the time indicator for the next circular buffer item * * - `writer` is our buffered output context * * Returns new idx that can be written or -1 if unchanged */ static int set_buffer_item_time(const buffered_TS_output_p writer, const int finalize) { switch (writer->pcr_mode) { case TSWRITE_PCR_MODE_PCR2: return finalize ? finalize_pcr_time(writer, &writer->pcr_pace) : add_pkt_pcr_time(writer, &writer->pcr_pace); case TSWRITE_PCR_MODE_PCR1: return set_buffer_item_time_pcr1(writer); case TSWRITE_PCR_MODE_NONE: default: // Allow the user to choose not to look at PCRs, and just do the // calculation based on the rate they've specified return set_buffer_item_time_plain(writer); } } // ============================================================ // EOF and the circular buffer // ============================================================ /* * Add a buffer entry that is flagged to mean "EOF" * * This is done by inserting a circular buffer entry with length 1 and * first data byte 1 (instead of the normal 0x47 transport stream sync byte). * * Returns 0 if all went well, 1 if something went wrong. */ static int add_eof_entry(buffered_TS_output_p writer) { circular_buffer_p circular = writer->buffer; int data_pos; int err = wait_if_buffer_full(circular); if (err) { print_err("### Internal error - waiting because circular buffer full\n"); return 1; } // Work out where we want to write data_pos = (circular->pending + 1) % circular->size; #if DISPLAY_BUFFER if (global_show_circular) fprint_msg("Parent: storing buffer %2d (EOF)\n",data_pos); #endif // Set the `time` within the item appropriately (it doesn't really // matter for EOF, since we're not actually going to *write* anything // out, but it won't hurt to get it right) set_buffer_item_time(writer, TRUE); // And mark EOF by setting the first byte to something that isn't 0x47, // and the length to 1. circular->item_data[data_pos * circular->item_size] = 1; circular->item[data_pos].length = 1; circular->end = data_pos; #if DISPLAY_BUFFER if (global_show_circular) print_circular_buffer("eof",circular); #endif circular->eos = TRUE; return 0; } // ============================================================ // Output via buffered TS output // ============================================================ /* * Flush the current circular buffer item. It must contain sensible data. * * - `writer` is our buffered output context * * Returns 0 if all went well, 1 if something went wrong. */ static void internal_flush_buffered_TS_output(const buffered_TS_output_p writer) { const circular_buffer_p circular = writer->buffer; int idx; if (!writer->started || circular->item[writer->which].length == 0) { // Nothing to do return; } // Set the `time` within the item appropriately idx = set_buffer_item_time(writer, FALSE); if (idx >= 0) circular->end = idx; // Make this item available for reading circular->pending = writer->which; // And then prepare for the next index writer->which = (circular->pending + 1) % circular->size; writer->started = FALSE; writer->num_packets = 0; writer->packet[0].got_pcr = FALSE; // Careful or paranoid? } static void discontinuity_buffered_TS_output(buffered_TS_output_p writer) { circular_buffer_p circular = writer->buffer; int idx; if (writer->pcr_mode != TSWRITE_PCR_MODE_PCR2) return; // Set the `time` within the item appropriately idx = discontinuity_pkt_pcr_time(writer, &writer->pcr_pace); if (idx >= 0) circular->end = idx; // We need to update the end of the circular buffer but we haven't added // any packets so no need to update any of that } /* * Write an EOF indicator to the buffered output * * This is done by flushing any current buffered output, and then * starting a new buffer item that contains a single byte, set to * 1 (in normal data, all TS packets start with 0x47, so this is * easily distinguished). The child fork knows that such a buffer * item signifies end of data. * * Returns 0 if all went well, 1 if something went wrong. */ static int write_EOF_to_buffered_TS_output(buffered_TS_output_p writer) { int err; // Make sure anything we were working on beforehand has been output internal_flush_buffered_TS_output(writer); if (global_parent_debug) print_msg("--> writing EOF\n"); err = add_eof_entry(writer); if (err) { print_err("### Error adding EOF indicator\n"); return 1; } return 0; } /* * Write the given TS packet out via the circular buffer. * * - `writer` is our buffered output * - `packet` is the TS packet * - `count` is its index in the input stream * - `pid` is its PID * - `got_pcr` is true if it contained a PCR * - in which case, `pcr` is the PCR * * Returns 0 if all went well, 1 if something went wrong. */ static int write_to_buffered_TS_output(buffered_TS_output_p writer, byte packet[TS_PACKET_SIZE], int count, uint32_t pid, int got_pcr, uint64_t pcr) { int err; const circular_buffer_p circular = writer->buffer; int which; byte *data; int *length; // Force PCRs to start a buffer if (writer->pcr_mode == TSWRITE_PCR_MODE_PCR2 && got_pcr) { // fprint_msg("got_pcr: %lld\n", pcr); internal_flush_buffered_TS_output(writer); } which = writer->which; data = circular->item_data + which*circular->item_size; length = &(circular->item[which].length); // If we haven't yet started writing to the (next) index in the // circular buffer, we must check that it is not full if (!writer->started) { err = wait_if_buffer_full(circular); if (err) { print_err("### Internal error - waiting because circular buffer full\n"); return 1; } writer->started = TRUE; writer->num_packets = 0; *length = 0; // fprint_msg("> "); } // fprint_msg("[%d] @ %d\n", writer->which, *length); // Copy our data into the circular buffer item, and adjust appropriately memcpy(&(data[*length]),packet,TS_PACKET_SIZE); (*length) += TS_PACKET_SIZE; // Allow the user to specify that PCRs are inflated/deflated if (got_pcr) { #if 0 fprint_msg("@@ PCR %10" LLU_FORMAT_STUMP " * %g",pcr,writer->pcr_scale); fprint_msg(" => %10" LLU_FORMAT_STUMP "\n", (uint64_t)(pcr*writer->pcr_scale)); #endif pcr = (uint64_t)((double)pcr * writer->pcr_scale); } else pcr = 0; // Remember the other data we'll need later on writer->packet[writer->num_packets].index = count; writer->packet[writer->num_packets].pid = pid; writer->packet[writer->num_packets].got_pcr = got_pcr; writer->packet[writer->num_packets].pcr = pcr; writer->num_packets ++; // Have we filled this entry in the circular buffer? if ((*length) >= circular->item_size - circular->hdr_size) internal_flush_buffered_TS_output(writer); return 0; } // ============================================================ // Child process - writing out data from the circular buffer // ============================================================ /* * Wait for a given number of microseconds (or longer). Must be < 1s. * * Note that on a "normal" Linux or BSD machine, the shortest wait possible * may be as long as 10ms (10000 microseconds) * * On Windows, this will actually wait for a number of milliseconds, * using 0 milliseconds if the number of microseconds is too small. * * Returns 0 if all goes well, 1 if something goes wrong. */ static void wait_microseconds(int microseconds) { #ifdef _WIN32 // Best we can (easily) do is to wait for the nearest (rounded down!) // number of milliseconds - hopefully this will do Sleep(microseconds / 1000); #else // _WIN32 struct timespec time = {0}; struct timespec remaining; uint32_t nanoseconds = microseconds * 1000; int err = 0; time.tv_sec = 0; time.tv_nsec = nanoseconds; errno = 0; err = nanosleep(&time,&remaining); while (err == -1 && errno == EINTR) // cope with being woken too early { errno = 0; time = remaining; err = nanosleep(&time,&remaining); } #endif // _WIN32 return; } /* * Write data out to a file * * - `tswriter` is the TS output context returned by `tswrite_open` * - `data` is the data to write out * - `data_len` is how much of it there is * * Returns 0 if all went well, 1 if something went wrong. */ static int write_file_data(TS_writer_p tswriter, byte data[], int data_len) { size_t written = 0; errno = 0; written = fwrite(data,1,data_len,tswriter->where.file); if (written != data_len) { fprint_err("### Error writing out TS packet data: %s\n",strerror(errno)); return 1; } return 0; } /* * Write data out to a socket * * - `output` is a socket for our output * - `data` is the data to write out * - `data_len` is how much of it there is * * Returns 0 if all went well, 1 if something went wrong. */ static int write_socket_data(SOCKET output, byte data[], int data_len) { #ifdef _WIN32 int written = 0; int left = data_len; #else // _WIN32 ssize_t written = 0; ssize_t left = data_len; #endif // _WIN32 int start = 0; // (When writing to a file, we don't expect to ever write less than // the requested number of bytes. However, if `output` is a socket, // it is possible that the underlying buffering might cause a // partial write.) errno = 0; while (left > 0) { written = send(output,&(data[start]),left,0); #ifdef _WIN32 if (written == SOCKET_ERROR) { int err = WSAGetLastError(); if (err == WSAENOBUFS) { print_err("!!! Warning: 'no buffer space available' writing out" " TS packet data - retrying\n"); } else { print_err("### Error writing out TS packet data:"); print_winsock_err(err); print_err("\n"); return 1; } } #else // _WIN32 if (written == -1) { if (errno == ENOBUFS) { print_err("!!! Warning: 'no buffer space available' writing out" " TS packet data - retrying\n"); errno = 0; } else { fprint_err("### Error writing out TS packet data: %s\n", strerror(errno)); return 1; } } #endif // _WIN32 left -= written; start += written; } return 0; } /* * Read a command character from the command input socket * * `command` comes in with the previous command character, and exits with * the current command character. `command_changed` is set TRUE if the * command character is changed, but *is not altered* if it is not * (i.e., it is up to someone else to "unset" `command_changed`). * * Returns 0 if all goes well, 1 if there is an error, and EOF if end-of-file * is read. */ static int read_command(SOCKET command_socket, byte *command, int *command_changed) { byte thing; #ifdef _WIN32 int length = recv(command_socket,&thing,1,0); #else ssize_t length = read(command_socket,&thing,1); #endif if (length == 0) { print_err("!!! EOF reading from command socket\n"); *command = COMMAND_QUIT; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[EOF -> quit]]\n"); #endif return 0; //return EOF; } #ifdef _WIN32 else if (length == SOCKET_ERROR) { int err = WSAGetLastError(); print_err("!!! Error reading from command socket:"); print_winsock_err(err); print_err("\n"); *command = COMMAND_QUIT; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[Error -> quit]]\n"); #endif return 0; //return 1; } #else else if (length == -1) { fprint_err("!!! Error reading from command socket: %s\n",strerror(errno)); *command = COMMAND_QUIT; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[Error -> quit]]\n"); #endif return 0; //return 1; } #endif switch (thing) { case 'q': *command = COMMAND_QUIT; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[quit]]\n"); #endif break; case 'n': *command = COMMAND_NORMAL; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[normal]]\n"); #endif break; case 'p': *command = COMMAND_PAUSE; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[pause]]\n"); #endif break; case 'f': *command = COMMAND_FAST; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[fast-forward]]\n"); #endif break; case 'F': *command = COMMAND_FAST_FAST; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[fast-fast-forward]]\n"); #endif break; case 'r': *command = COMMAND_REVERSE; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[reverse]]\n"); #endif break; case 'R': *command = COMMAND_FAST_REVERSE; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[fast-reverse]]\n"); #endif break; case '>': *command = COMMAND_SKIP_FORWARD; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[skip-forward]]\n"); #endif break; case '<': *command = COMMAND_SKIP_BACKWARD; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[skip-backward]]\n"); #endif break; case ']': *command = COMMAND_SKIP_FORWARD_LOTS; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[big-skip-forward]]\n"); #endif break; case '[': *command = COMMAND_SKIP_BACKWARD_LOTS; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[big-skip-backward]]\n"); #endif break; case '0': *command = COMMAND_SELECT_FILE_0; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-0]]\n"); #endif break; case '1': *command = COMMAND_SELECT_FILE_1; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-1]]\n"); #endif break; case '2': *command = COMMAND_SELECT_FILE_2; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-2]]\n"); #endif break; case '3': *command = COMMAND_SELECT_FILE_3; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-3]]\n"); #endif break; case '4': *command = COMMAND_SELECT_FILE_4; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-4]]\n"); #endif break; case '5': *command = COMMAND_SELECT_FILE_5; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-5]]\n"); #endif break; case '6': *command = COMMAND_SELECT_FILE_6; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-6]]\n"); #endif break; case '7': *command = COMMAND_SELECT_FILE_7; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-7]]\n"); #endif break; case '8': *command = COMMAND_SELECT_FILE_8; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-8]]\n"); #endif break; case '9': *command = COMMAND_SELECT_FILE_9; *command_changed = TRUE; #if DEBUG_COMMANDS print_msg("[[select-file-9]]\n"); #endif break; case '\n': // Newline is needed to send commands to us #if DEBUG_COMMANDS print_msg("[[newline/ignored]]\n"); #endif break; // so ignore it silently default: #if DEBUG_COMMANDS fprint_msg("[[%c ignored]]\n",(isprint(thing)?thing:'?')); #endif break; } return 0; } /* * Write data out to a socket using TCP/IP (and maybe reading commands as well) * * - `tswriter` is the TS output context returned by `tswrite_open` * - `data` is the data to write out * - `data_len` is how much of it there is. If this is 0, then we will * not write any data (or, if command input is enabled, wait for permission * to write data). * * Returns 0 if all went well, 1 if something went wrong. */ static int write_tcp_data(TS_writer_p tswriter, byte data[], int data_len) { int err; if (tswriter->command_socket == -1) { if (data_len == 0) return 0; // If we're not soliciting commands, then our output socket will // be blocking, and we can just write to it... err = write_socket_data(tswriter->where.socket,data,data_len); if (err) return 1; } else { // Otherwise, we must check for command input, and also whether our // output socket is ready to be written to int not_written = TRUE; fd_set read_fds, write_fds; #if DEBUG_DATA_WAIT int waiting = FALSE; #endif int num_to_check = max((int)tswriter->command_socket, (int)tswriter->where.socket) + 1; while (not_written) { int result; FD_ZERO(&read_fds); FD_ZERO(&write_fds); // Only look for a new command if the last is not still outstanding // (remember, it is up to our caller to unset the "command changed" flag) if (!tswriter->command_changed) FD_SET(tswriter->command_socket,&read_fds); if (data_len > 0) FD_SET(tswriter->where.socket,&write_fds); result = select(num_to_check,&read_fds,&write_fds,NULL,NULL); if (result == -1) { fprint_err("### Error in select: %s\n",strerror(errno)); return 1; } else if (result == 0) // Hmm - wouldn't expect this continue; // So try again if (FD_ISSET(tswriter->command_socket,&read_fds)) { err = read_command(tswriter->command_socket, &tswriter->command,&tswriter->command_changed); if (err) return 1; } // Note that, unless we've quit, we always write out the outstanding // packet if we have been told that we *can* write. if (FD_ISSET(tswriter->where.socket,&write_fds)) { err = write_socket_data(tswriter->where.socket,data,data_len); if (err) return 1; not_written = FALSE; } else if (data_len == 0) not_written = FALSE; // well, sort of #if DEBUG_DATA_WAIT if (not_written) { waiting = TRUE; fprint_msg(".. still waiting to write data (last command '%c', %s)..\n", (isprint(tswriter->command)?tswriter->command:'?'), (tswriter->command_changed?"changed":"unchanged")); } else if (waiting) { waiting = FALSE; fprint_msg(".. data written (last command '%c', %s)..\n", (isprint(tswriter->command)?tswriter->command:'?'), (tswriter->command_changed?"changed":"unchanged")); } #endif } } return 0; } /* * Wait for a new command after 'p'ausing. * * - `tswriter` is the TS output context returned by `tswrite_open` * * Returns 0 if all went well, 1 if something went wrong. */ extern int wait_for_command(TS_writer_p tswriter) { if (tswriter->command_socket == -1) { print_err("### Cannot wait for new command when command input" " is not enabled\n"); return 1; } else { int err; fd_set read_fds; int num_to_check = (int)tswriter->command_socket + 1; FD_ZERO(&read_fds); while (!tswriter->command_changed) { int result; FD_SET(tswriter->command_socket,&read_fds); result = select(num_to_check,&read_fds,NULL,NULL,NULL); if (result == -1) { fprint_err("### Error in select: %s\n",strerror(errno)); return 1; } else if (result == 0) // Hmm - wouldn't expect this continue; // So try again if (FD_ISSET(tswriter->command_socket,&read_fds)) { err = read_command(tswriter->command_socket, &tswriter->command,&tswriter->command_changed); if (err) return 1; } } return 0; } } /* * Write the next data item in our buffer * * - `output` is a socket for our output * - `circular` is our circular buffer of "packets" * * Returns 0 if all went well, 1 if something went wrong. */ static int write_circular_data(const SOCKET output, const circular_buffer_p circular) { int err; byte *buffer = circular->item_data + circular->start*circular->item_size - circular->hdr_size; int length = circular->item[circular->start].length + circular->hdr_size; #if DISPLAY_BUFFER int oldend = circular->pending; int oldstart = circular->start; int newend,newstart; #endif err = write_socket_data(output,buffer,length); if (err) { // If we're writing out over UDP, it's possible our write fails for // some reason. In general, it's best for us to ignore this, so that // the parent process can just keep dumping data to us, and we can // keep trying to write it. // In fact, probably the best thing to do is just *ignore* the error // at this level (write_socket_data will already have output some sort // of error or warning message). } #if DISPLAY_BUFFER if (global_show_circular) { newend = circular->pending; newstart = circular->start; if (oldend != newend || oldstart != newstart) { fprint_msg("get [%2d,%2d] became [%2d,%2d]", oldend,oldstart,newend,newstart); if (oldstart != newstart) print_msg(" (!!)"); if (newstart == (newend + 1) % circular->size) print_msg(" ->empty"); if ((newend + 2) % circular->size == newstart) print_msg(" ->full"); print_msg("\n"); } } #endif // Once we've finished writing it, we can relinquish this entry in // the circular buffer buffer[0] = 0; // just for debug output's sake circular->start = (circular->start + 1) % circular->size; #if DISPLAY_BUFFER if (global_show_circular) print_circular_buffer("<--",circular); #endif return 0; } /* * Check if we have received an end-of-file indicator * * - `circular` is our circular buffer of "packets" * * Returns TRUE if we have received an end-of-file indicator, FALSE * if not. */ static int received_EOF(circular_buffer_p circular) { byte *buffer = circular->item_data + circular->start*circular->item_size; int length = circular->item[circular->start].length; if (length == 1 && buffer[0] == 1) { // Relinquish the buffer entry, just in case... circular->start = (circular->start + 1) % circular->size; #if DISPLAY_BUFFER if (global_show_circular) { print_msg("Child: found EOF\n"); print_circular_buffer("<--",circular); } #else if (child_parent_debug) print_msg("<-- found EOF\n"); #endif return TRUE; } else return FALSE; } /* * Calculate a value to perturb time by. Returns a number of microseconds. */ static int32_t perturb_time_by(void) { static int first_time = TRUE; unsigned double_range; int32_t result; if (first_time) { if (global_perturb_verbose) fprint_msg("... perturb seed %ld, range %u\n", (long)global_perturb_seed,(unsigned)global_perturb_range); srand(global_perturb_seed); first_time = FALSE; } // We want values in the range - .. // So double the range to give us a number we can shift downwards // by to get negative numbers as well, and add one to // so we get 0.. instead of 0..-1. double_range = (global_perturb_range+1) * 2; result = (unsigned int)((double)double_range * ((double)rand() / (RAND_MAX + 1.0))); // Shift it to give range centred on zero result -= global_perturb_range; if (global_perturb_verbose) fprint_msg("... perturb %ldms\n",(long)result); return result * 1000; } /* * Write the next data item in our buffer * * - `output` is a socket for our output * - `circular` is our circular buffer of "packets" * - if `quiet` then don't output extra messages (about filling up * circular buffer) * - `had_eof` is set TRUE if we read a packet flagged to indicate * that it is the end of data - this is how we know when to stop. * * Returns 0 if all went well, 1 if something went wrong. */ static int write_from_circular(SOCKET output, circular_buffer_p circular, int quiet, int *had_eof) { int err; // Are we starting up for the first time? static int starting = TRUE; // Do we need to (re)set our relative timeline? At the start we do. static int reset = TRUE; // Monitor time as seen by the parent // The parent prefixes each circular buffer item with the time // (in microseconds since some arbitrary start time) at which it would // like it to be displayed. For a constant rate bitstream, these "ticks" // will be evenly spaced. uint32_t this_packet_time; // time stamp for this packet static uint32_t last_packet_time = 0; // time stamp for last packet int32_t packet_time_gap; // the difference between the two, in microseconds // Monitor time as seen by us // We have to deduce both an arbitrary start time from which to measure // "ticks", and also when we should (according to the requested gaps, // and the progress through time) be outputing the next packet - i.e., // as near to the correct tick as possible. struct timeval now; static struct timeval start = {0,0}; // our arbitrary start time uint32_t our_time_now; // our time, relative to our start time static int32_t delta_start; // difference between our time and the parent's uint32_t adjusted_now; // our time, adjusted by delta_start int32_t waitfor; // how long we think we need to wait to adjust // How many items have we sent without *any* delay? // (not used if maxnowait is off) static int sent_without_delay = 0; // When grumbling about having had to restart our time sequence, // it is nice to be able to say which packet we were outputting // (so the user can tell how frequently we're doing this) static unsigned int count = 0; count ++; if (starting) { // If we're starting up for the first time, it's probably worth waiting // for the circular buffer to fill up if (!quiet) print_msg("Circular buffer filling...\n"); err = wait_for_buffer_to_fill(circular); if (err) { print_err("### Error - waiting for circular buffer to fill\n"); return 1; } if (!quiet) print_msg("Circular buffer filled - starting to send data\n"); starting = FALSE; } else { // If the buffer is empty, there's really not much else we can do but // wait for it not to be empty. err = wait_if_buffer_empty(circular); if (err) { print_err("### Error - waiting because circular buffer is empty\n"); return 1; } } // If the next item is an end-of-file indicator, we can exit at once // - we don't need to wait for the right time to "write" it if (received_EOF(circular)) { *had_eof = TRUE; return 0; } // Work out the interval that the parent is asking for this_packet_time = circular->item[circular->start].time; packet_time_gap = this_packet_time - last_packet_time; // Work out the actual position on our own timeline gettimeofday(&now, NULL); // We're *actually* at this distance along our time line our_time_now = (now.tv_sec - start.tv_sec) * 1000000 + (now.tv_usec - start.tv_usec); if (global_perturb_range) { // Add a (positive or negative) delta to that so that our // time appears to jump around a bit, hopefully leading to // an output that looks like an unreliable network delay our_time_now += perturb_time_by(); } // Check whether we've asked for a reset, or if the parent process // has told us that the timeline has changed radically if (reset || circular->item[circular->start].discontinuity) { // fprint_msg("%s: Discontinuity[%d]: reset=%d, pkt_time=%u\n", __func__, circular->start, reset, this_packet_time); // We believe out timeline has gone askew - start a new one // Set up "now" as our base time, and output our packet right away start = now; our_time_now = 0; delta_start = this_packet_time; waitfor = 0; if (global_child_debug) fprint_msg("<-- packet %6u, gap %6u; STARTING delta %6d ", this_packet_time,packet_time_gap,delta_start); reset = FALSE; } else { // We can try to relate that to the parent's timeline adjusted_now = our_time_now + delta_start; // So how long do we (notionally) need to wait for the right time? waitfor = this_packet_time - adjusted_now; if (global_child_debug) fprint_msg("<-- packet %6u, gap %6u; our time %6u = %6u -> wait %6d ", this_packet_time,packet_time_gap,our_time_now,adjusted_now, waitfor); } // So how long *should* we wait for the correct time to write? if (waitfor > 0) { if (waitfor > 200000) { fprint_msg("###[%d] (%d) >0.2s, RESET\n", circular->start, waitfor); reset = TRUE; waitfor = 200000; } if (global_child_debug) print_msg("(waiting"); } else if (waitfor > -200000) // less than 0.2 seconds gap - "small", so ignore { if (global_child_debug) print_msg("(<0.2s, ignore"); waitfor = 0; } else // more than 0.2 seconds - makes us reset our idea of time { if (global_perturb_range == 0) // but only if we're not mucking about with time { if (global_child_debug) print_msg("(>0.2s, RESET"); else { // Let the user know we're having some problems. // Use the amended `count` as the primary index since the parent // process logs progress in terms of the number of TS packets // output - (count-1)*7+1 should be the index of the first packet // in our circular buffer item, which is a decent approximation fprint_err("!!! [%d] Packet %d (item %d): Outputting %.2fs late -" " restarting time sequence: time=%u\n", circular->start, (count-1)*7+1,count,-(double)waitfor/1000000, this_packet_time); if (circular->maxnowait >= 0) fprint_err(" Maybe consider running with -maxnowait greater" " than %d\n",circular->maxnowait); } // Ask for a reset, and output the packet right away reset = TRUE; waitfor = 0; } } // We are not allowed to send more than three consecutive packets // with no delay (or we might swamp the receiving hardware) if (waitfor == 0 && circular->maxnowait != -1) { if (sent_without_delay < circular->maxnowait) { sent_without_delay ++; if (global_child_debug) fprint_msg(", %d)\n",sent_without_delay); } else { if (global_child_debug) fprint_msg(", %d -> wait)\n", sent_without_delay+1); waitfor = circular->waitfor; // enforce a minimal wait } } else if (global_child_debug) print_msg(")\n"); // So, finally, do we need to wait before writing? if (waitfor > 0) { wait_microseconds(waitfor); sent_without_delay = 0; } // Write it... err = write_circular_data(output,circular); if (err) return 1; // Don't forget to update our memory before we finish last_packet_time = this_packet_time; return 0; } /* * The child process just writes the contents of the circular buffer out, * as it receives it. * * - `tswriter` is the context to use for writing TS output * * The intent is that, after forking, all the code needs to do is:: * * else if (pid == 0) * { * _exit(tswrite_child_process(tswriter)); * } * * and the child process will "just work". * * Note that the end of the data to read/write is detected when a * circular buffer entry with length 1 and first data byte 1 is found * (see `write_EOF_to_buffered_TS_output()`) * * Returns the value that should be returned by the the child process * (0 for success, 1 for failure). */ static int tswrite_child_process(TS_writer_p tswriter) { int had_eof = FALSE; for (;;) { int err = write_from_circular(tswriter->where.socket, tswriter->writer->buffer, tswriter->quiet, &had_eof); if (err) return 1; if (had_eof) break; } return 0; } #ifdef _WIN32 // ============================================================ // Windows threading ("fork" alternative) // ============================================================ /* * Wrapper for tswrite_child_process, used to coerce args, etc. */ static void child_thread_fn(void_p arg) { TS_writer_p tswriter = (TS_writer_p)arg; (void) tswrite_child_process(tswriter); #ifdef _WIN32 { int err; // On Windows, only the "child" knows when it has finished using its // resources (i.e., the circular buffer and output socket), so only the // "child" can sensibly release them... err = disconnect_socket(tswriter->where.socket); if (err == EOF) fprint_err("### Error closing output: %s\n",strerror(errno)); // And free the buffering stuff err = free_buffered_TS_output(&(tswriter->writer)); if (err) print_err("### Error freeing TS buffer\n"); free(tswriter); } #endif } /* * Start up the child thread, to handle the circular buffering */ static int start_child(TS_writer_p tswriter) { tswriter->child = (HANDLE) _beginthread(child_thread_fn,0,(void_p)tswriter); if (tswriter->child == (HANDLE) -1) { fprint_err("Error creating child process: %s\n",strerror(errno)); return 1; } return 0; } #else // _WIN32 // ============================================================ // Unix forking ("thread" alternative) // ============================================================ /* * Start up the child fork, to handle the circular buffering */ static int start_child(TS_writer_p tswriter) { pid_t pid; tswriter->child = 0; pid = fork(); if (pid == -1) { fprint_err("Error forking: %s\n",strerror(errno)); return 1; } else if (pid == 0) { // Aha - we're the child _exit(tswrite_child_process(tswriter)); } // Otherwise, we're the parent - carry on tswriter->child = pid; return 0; } /* * Wait for the child fork to exit */ static int wait_for_child_to_exit(TS_writer_p tswriter, int quiet) { int err; pid_t result; if (!quiet) print_msg("Waiting for child to finish writing and exit\n"); result = waitpid(tswriter->child,&err,0); if (result == -1) { fprint_err("### Error waiting for child to exit: %s\n", strerror(errno)); return 1; } if (WIFEXITED(err)) { if (!quiet) print_msg("Child exited normally\n"); } tswriter->child = 0; return 0; } #endif // _WIN32 // ============================================================ // Writing // ============================================================ /* * * Build the basics of a TS writer context. * * Returns 0 if all goes well, 1 if something went wrong. */ static int tswrite_build(TS_WRITER_TYPE how, int quiet, TS_writer_p *tswriter) { TS_writer_p new = NULL; new = malloc(SIZEOF_TS_WRITER); if (new == NULL) { print_err("### Unable to allocate space for TS_writer datastructure\n"); return 1; } new->how = how; new->writer = NULL; new->child = 0; new->count = 0; new->quiet = quiet; new->server = FALSE; // not being a server new->command_socket = -1; // not taking commands new->command = COMMAND_PAUSE; // start in pause new->command_changed = FALSE; // no new command new->atomic_command = FALSE; // but any command is interruptable new->drop_packets = 0; *tswriter = new; return 0; } /* * Open a file for TS output. * * - `how` is how to open the file or connect to the host * - `name` is the name of the file or host to open/connect to * (this is ignored if `how` is TS_W_STDOUT) * - if `how` is TS_W_UDP, and `name` is a multicast address, * then `multicast_if` is the IP address of the network * address to use, or NULL if the default interface should * be used. If `how` is not TS_W_UDP, `multicast_if` is ignored. * - if it is a socket (i.e., if `how` is TS_W_TCP or TS_W_UDP), * then `port` is the port to use, otherwise this is ignored * - `quiet` is true if only error messages should be printed * - `tswriter` is the new context to use for writing TS output, * which should be closed using `tswrite_close`. * * For TS_W_STDOUT, there is no need to open anything. * * For TS_W_FILE, ``open(name,O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,00777)`` * is used - i.e., the file is opened so that anyone may read/write/execute * it. If ``O_BINARY`` is not defined (e.g., on Linux), then it is * omitted. * * For TS_W_TCP and TS_W_UDP, the ``connect_socket`` function is called, * which uses ``socket`` and ``connect``. * * In all cases (even when using TS_W_STDOUT), the `tswriter` should be * closed using `tswrite_stdout`. * * For TS_W_UDP, the ``tswrite_start_buffering`` function must be called * before any output is written via the `tswriter`. For the other forms of * output, this is not allowed. * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_open(TS_WRITER_TYPE how, char *name, char *multicast_if, int port, int quiet, TS_writer_p *tswriter) { TS_writer_p new; int err = tswrite_build(how,quiet,tswriter); if (err) return 1; new = *tswriter; switch (how) { case TS_W_STDOUT: if (!quiet) print_msg("Writing to \n"); new->where.file = stdout; break; case TS_W_FILE: if (!quiet) fprint_msg("Writing to file %s\n",name); new->where.file = fopen(name,"wb"); if (new->where.file == NULL) { fprint_err("### Unable to open output file %s: %s\n", name,strerror(errno)); return 1; } break; case TS_W_TCP: if (!quiet) fprint_msg("Connecting to %s via TCP/IP on port %d\n", name,port); new->where.socket = connect_socket(name,port,TRUE, NULL); if (new->where.socket == -1) { fprint_err("### Unable to connect to %s\n",name); return 1; } if (!quiet) fprint_msg("Writing to %s via TCP/IP\n",name); break; case TS_W_UDP: if (!quiet) { // We don't *know* at this stage if the `name` *is* a multicast address, // but we'll assume the user only specifies `multicast_if` is it is, for // the purposes of these messages (amending `connect_socket`, which does // know, to output this message iff `!quiet` is a bit overkill) fprint_msg("Connecting to %s via UDP on port %d",name,port); if (multicast_if) fprint_msg(" (multicast interface %s)",multicast_if); print_msg("\n"); } new->where.socket = connect_socket(name,port,FALSE,multicast_if); if (new->where.socket == -1) { fprint_err("### Unable to connect to %s\n",name); return 1; } if (!quiet) fprint_msg("Writing to %s via UDP\n",name); break; default: fprint_err("### Unexpected writer type %d to tswrite_open()\n",how); free(new); return 1; } return 0; } /* * Open a network connection for TS output. * * This is a convenience wrapper around `tswrite_open`. * * - `name` is the name of the host to connect to * - `port` is the port to connect to * - `use_tcp` is TRUE if TCP/IP should be use, FALSE if UDP should be used * - `quiet` is true if only error messages should be printed * - `tswriter` is the new context to use for writing TS output, * which should be closed using `tswrite_close`. * * In all cases, the `tswriter` should be closed using `tswrite_stdout`. * * For TS_W_UDP, the ``tswrite_start_buffering`` function must be called * before any output is written via the `tswriter`. For other forms of output, * this not allowed. * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_open_connection(int use_tcp, char *name, int port, int quiet, TS_writer_p *tswriter) { return tswrite_open((use_tcp?TS_W_TCP:TS_W_UDP),name,NULL,port,quiet,tswriter); } /* * Open a file for TS output. * * This is a convenience wrapper around `tswrite_open`. * * - `name` is the name of the file to open, or NULL if stdout should be used * - `quiet` is true if only error messages should be printed * - `tswriter` is the new context to use for writing TS output, * which should be closed using `tswrite_close`. * * In all cases (even when using TS_W_STDOUT), the `tswriter` should be * closed using `tswrite_stdout`. * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_open_file(char *name, int quiet, TS_writer_p *tswriter) { return tswrite_open((name==NULL?TS_W_STDOUT:TS_W_FILE),name,NULL,0,quiet, tswriter); } /* * Wait for a client to connect and then both write TS data to it and * listen for command from it. Uses TCP/IP. * * - `server_socket` is the socket on which we will listen for a connection * - `quiet` is true if only error messages should be printed * - `tswriter` is the new context to use for writing TS output, * which should be closed using `tswrite_close`. * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_wait_for_client(int server_socket, int quiet, TS_writer_p *tswriter) { TS_writer_p new; int err = tswrite_build(TS_W_TCP,quiet,tswriter); if (err) return 1; new = *tswriter; new->server = TRUE; // Listen for someone to connect to it err = listen(server_socket,1); if (err == -1) { #ifdef _WIN32 err = WSAGetLastError(); print_err("### Error listening for client: "); print_winsock_err(err); print_err("\n"); #else // _WIN32 fprint_err("### Error listening for client: %s\n",strerror(errno)); #endif // _WIN32 return 1; } // Accept the connection new->where.socket = accept(server_socket,NULL,NULL); if (new->where.socket == -1) { #ifdef _WIN32 err = WSAGetLastError(); print_err("### Error accepting connection: "); print_winsock_err(err); print_err("\n"); #else // _WIN32 fprint_err("### Error accepting connection: %s\n",strerror(errno)); #endif // _WIN32 return 1; } return 0; } /* * Set up internal buffering for TS output. This is necessary for UDP * output, and not allowed for other forms of output. * * 1. Builds the internal circular buffer and other datastructures * 2. Starts a child process to read from the circular buffer and send * data over the socket. * 3. Starts a parent process which calls the supplied function, which * is expected to use `tswrite_write()` to write to the circular * buffer. * * See also `tswrite_start_buffering_from_context`, which uses the `context` * datastructure that is prepared by `tswrite_process_args`. * * - `tswriter` is the TS output context returned by `tswrite_open` * - `circ_buf_size` is the number of buffer entries (plus one) we would * like in the underlying circular buffer. * - `TS_in_packet` is the number of TS packets to allow in each network * packet. * - `maxnowait` is the maximum number of packets to send to the target * host with no wait between packets * - `waitfor` is the number of microseconds to wait for thereafter * - `byterate` is the (initial) rate at which we'd like to output our data * - `use_pcrs` is TRUE if PCRs in the data stream are to be used for * timing output (the normal case), otherwise the specified byte rate * will be used directly. * - `prime_size` is how much to prime the circular buffer output timer * - `prime_speedup` is the percentage of "normal speed" to use for the priming * rate. This should normally be set to 100 (i.e., no effect). * - `pcr_scale` determines how much to "accelerate" each PCR - see the * notes elsewhere on how this works. * * Returns 0 if all went well, 1 if something went wrong. */ extern int tswrite_start_buffering(TS_writer_p tswriter, int circ_buf_size, int TS_in_packet, int maxnowait, int waitfor, int byterate, tswrite_pcr_mode pcr_mode, int prime_size, int prime_speedup, double pcr_scale, const tswrite_pkt_hdr_type_t hdr_type) { int err; if (tswriter->how != TS_W_UDP) { fprint_err("### Buffered output not supported for %s output\n", (tswriter->how == TS_W_TCP?"TCP/IP": tswriter->how == TS_W_FILE?"file": tswriter->how == TS_W_STDOUT?"":"???")); return 1; } err = build_buffered_TS_output(&(tswriter->writer), circ_buf_size,TS_in_packet, maxnowait,waitfor,byterate,pcr_mode, prime_size,prime_speedup,pcr_scale, hdr_type); if (err) return 1; err = start_child(tswriter); if (err) { (void) free_buffered_TS_output(&tswriter->writer); return 1; } return 0; } /* * Set up internal buffering for TS output. This is necessary for UDP * output, and not allowed for other forms of output. * * This alternative takes the `context` datastructure that is prepared * by `tswrite_process_args`. * * - `tswriter` is the TS output context returned by `tswrite_open` * - `context` contains the necessary information, as given by the user * * Returns 0 if all went well, 1 if something went wrong. */ extern int tswrite_start_buffering_from_context(TS_writer_p tswriter, TS_context_p context) { return tswrite_start_buffering(tswriter, context->circ_buf_size, context->TS_in_item, context->maxnowait, context->waitfor, context->byterate, context->pcr_mode, context->prime_size, context->prime_speedup, context->pcr_scale, context->pkt_hdr_type); } /* * Indicate to a TS output context that `input` is to be used as * command input. * * This function may only be used if output is via TCP/IP. * * - `tswriter` is the TS output context returned by `tswrite_open` * - `input` is the socket (or, on Linux/BSD, file descriptor) on which * to listen for commands. * * Note that this should either be ``tswriter->where.socket`` or * STDIN_FILENO - no other values are currently supported (particularly * since no attempt is made to close this socket when things are finished, * which doesn't matter for the given values). * * This function: * * - makes the socket on which data will be written non-blocking * (i.e., if the socket is not ready to be written to, it will not * accept input and block until it can be used, which means that it * becomes our responsibility to ask if the socket is ready for output) * - makes tswrite_write "look" on the `input` to see if a (single * character) command has been given, and if it has, put it into * the `tswriter` datastructure for use * * The command state is set to 'p'ause - i.e., as if the client had sent * a COMMAND_PAUSE command. * * Returns 0 if all went well, 1 if something went wrong. */ extern int tswrite_start_input(TS_writer_p tswriter, SOCKET input) { int err; #ifdef _WIN32 u_long one = 1; #else int flags; #endif // _WIN32 if (tswriter->how != TS_W_TCP) { print_err("### Command input is only supported for TCP/IP\n"); return 1; } // Make our output socket non-blocking #ifdef _WIN32 err = ioctlsocket(tswriter->where.socket,FIONBIO,&one); if (err == SOCKET_ERROR) { err = WSAGetLastError(); print_err("### Unable to set socket nonblocking: "); print_winsock_err(err); print_err("\n"); return 1; } #else // _WIN32 flags = fcntl(tswriter->where.socket,F_GETFL,0); if (flags == -1) { fprint_err("### Error getting flags for output socket: %s\n", strerror(errno)); return 1; } err = fcntl(tswriter->where.socket,F_SETFL,flags | O_NONBLOCK); if (err == -1) { fprint_err("### Error setting output socket non-blocking: %s\n", strerror(errno)); return 1; } #endif // _WIN32 tswriter->command_socket = input; tswriter->command = COMMAND_PAUSE; return 0; } /* * Set/unset "atomic" status - i.e., whether a command may be interrupted * by the next command. * * Most commands (normal play, fast forwards, etc.) should be interrupted * by a new command. However, some (the skip forwards and backwards commands) * make sense only if they will always complete. This function allows that * state to be toggled. */ extern void tswrite_set_command_atomic(TS_writer_p tswriter, int atomic) { tswriter->atomic_command = atomic; } /* * Ask a TS writer if changed input is available. * * If the TS writer is enabled for command input, then if the command * currently being executed has declared itself "atomic" (i.e., not able to be * interrupted), it returns FALSE, otherwise it returns TRUE if the command * character has changed. */ extern int tswrite_command_changed(TS_writer_p tswriter) { if (tswriter->command_socket == -1) return FALSE; else { if (tswriter->atomic_command) return FALSE; else return tswriter->command_changed; } } /* * Finish off buffered output, and wait for the child to exit * * - `tswriter` is the TS output context returned by `tswrite_open` * - `quiet` should be true if only error messages are to be output * * Returns 0 if all goes well, 1 if something went wrong. */ static int tswrite_close_child(TS_writer_p tswriter, int quiet) { int err; if (tswriter->writer == NULL) return 0; if (tswriter->child == 0) return 0; if (tswriter->writer) { // We're writing to a child through a circular buffer // Indicate "end of file" to the child err = write_EOF_to_buffered_TS_output(tswriter->writer); if (err) { print_err("### Error adding EOF indicator to TS buffer\n"); (void) free_buffered_TS_output(&tswriter->writer); return 1; } } #ifndef _WIN32 // On Linux/BSD, we have forked, and thus it is reasonable for the parent // process to tidy up when it has finished (since the child process is in // separate memory space). On Windows, this has to be done by the "child". // So wait for the child to complete err = wait_for_child_to_exit(tswriter,quiet); if (err) { (void) free_buffered_TS_output(&tswriter->writer); return 1; } if (tswriter->writer) { // And free the shared memory resources err = free_buffered_TS_output(&(tswriter->writer)); if (err) { print_err("### Error freeing TS buffer\n"); return 1; } } #endif // not _WIN32 return 0; } /* * Close a file or socket. * * - `tswriter` is the TS output context returned by `tswrite_open` * * Returns 0 if all goes well, 1 if something went wrong. */ static int tswrite_close_file(TS_writer_p tswriter) { int err; switch (tswriter->how) { case TS_W_STDOUT: // Nothing to do for standard output break; case TS_W_FILE: err = fclose(tswriter->where.file); if (err == EOF) { fprint_err("### Error closing output: %s\n",strerror(errno)); return 1; } break; case TS_W_TCP: case TS_W_UDP: err = disconnect_socket(tswriter->where.socket); if (err == EOF) { fprint_err("### Error closing output: %s\n",strerror(errno)); return 1; } break; default: fprint_err("### Unexpected writer type %d to tswrite_close()\n", tswriter->how); return 1; } return 0; } /* * Close a file or socket opened using `tswrite_open`, and if necessary, * send the child process used for output buffering an end-of-file * indicator, and wait for it to finish. * * Also frees the TS writer datastructure. * * - `tswriter` is the TS output context returned by `tswrite_open` * - if `quiet` is true, then waiting for the child to exit should * not be reported on (i.e., only errors should produce output) * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_close(TS_writer_p tswriter, int quiet) { int err; if (tswriter == NULL) return 0; // Only does anything if there *is* a child to close/buffer to shut down err = tswrite_close_child(tswriter,quiet); if (err) { print_err("### Error closing child process\n"); #ifdef _WIN32 if (!tswriter->writer) { #endif (void) tswrite_close_file(tswriter); free(tswriter); #ifdef _WIN32 } #endif return 1; } #ifdef _WIN32 if (tswriter->writer) { // We're doing buffered output. On Windows, this means that we are using a // parent thread and a child thread. Only one thread should close/free the // remaining resources, and since only the child thread knows when it // stops, it has to be the child thread that does it. This function is // called by the parent thread, so it should not. Moreover, having asked // the child thread to shut down (above), it cannot tell when the child // will have free the tswriter, so it must not refer to it again... } else { #endif err = tswrite_close_file(tswriter); if (err) { print_err("### Error closing output\n"); free(tswriter); return 1; } if (!quiet) fprint_msg("Output %d TS packets\n",tswriter->count); free(tswriter); #ifdef _WIN32 } #endif return 0; } /* * Write a Transport Stream packet out via the TS writer. * * - `tswriter` is the TS output context returned by `tswrite_open` * - `packet` is the TS packet * - if the packets payload_unit_start_indicator is set, then * `pid` is the PID for this packet, `got_pcr` is TRUE if it * contains a PCR in its adaptation field, and `pcr` contains * said PCR. These values are only used when outputting via * buffered output. * * Returns 0 if all goes well, 1 if something went wrong, and EOF if command * input is enabled (only allowed for TCP/IP output) and the 'q'uit command * has been given (in which case, no further commands will be read, and no * more output will be written, by any subsequent calls of this function). */ extern int tswrite_write(TS_writer_p tswriter, byte packet[TS_PACKET_SIZE], uint32_t pid, int got_pcr, uint64_t pcr) { int err; if (tswriter->drop_packets) { // Output drop_packets packets, and then omit drop_number static int packet_count = 0; static int drop_count = 0; if (drop_count > 0) // we're busy ignoring packets { #if 0 print_msg("x"); #endif drop_count --; return 0; } else if (packet_count < tswriter->drop_packets) { #if 0 if (packet_count == 0) print_msg("\n"); print_msg("."); #endif packet_count ++; } else { #if 0 print_msg("X"); #endif packet_count = 0; drop_count = tswriter->drop_number - 1; return 0; } } if (tswriter->writer == NULL) { // We're writing directly switch (tswriter->how) { case TS_W_STDOUT: case TS_W_FILE: err = write_file_data(tswriter,packet,TS_PACKET_SIZE); if (err) return 1; break; case TS_W_TCP: err = write_tcp_data(tswriter,packet,TS_PACKET_SIZE); if (err) return err; // important, because it might be 0, 1 or EOF break; case TS_W_UDP: err = write_socket_data(tswriter->where.socket,packet,TS_PACKET_SIZE); if (err) return 1; break; default: fprint_err("### Unexpected writer type %d to tswrite_write()\n", tswriter->how); return 1; } (tswriter->count)++; } else { // We're writing via buffered output err = write_to_buffered_TS_output(tswriter->writer,packet, (tswriter->count)++, pid,got_pcr,pcr); if (err) return 1; } return 0; } /* * Discontinuity on the stream being written (e.g. file looping) * If we are pacing the output then this resets the timing info */ int tswrite_discontinuity(const TS_writer_p tswriter) { if (tswriter->writer == NULL) return 0; internal_flush_buffered_TS_output(tswriter->writer); discontinuity_buffered_TS_output(tswriter->writer); return 0; } // ============================================================ // Common option handling - helpers for utility writers // ============================================================ /* * Write a usage string (to standard output) describing the tuning * options processed by tswrite_process_args. */ extern void tswrite_help_tuning(void) { fprint_msg( "Output Tuning:\n" " -bitrate Try for an initial data rate of bits/second,\n" " so -bitrate 3000 is 3000 bits/second, i.e., 3kbps\n" " -byterate Specify the initial data rate in bytes per second,\n" " instead of bits/second.\n" " -nopcrs Ignore PCRs when working out the packet times,\n" " just use the selected bit/byte rate.\n" "\n" "The data rate is stored internally as bytes/second, so if a -bitrate value\n" "is given that is not a multiple of 8, it will be approximated internally.\n" "If no initial data rate is specified, an arbitrary default rate of\n" "%d bytes/second (%d bits/second) is used. If the input data contains\n" "PCRs, this will then be adjusted towards the data rate indicated by\n" "the PCRs.\n" "\n" " -maxnowait Specify the maximum number of packets that can be\n" " sent to the target host with no gap. Sending too\n" " many packets with no gap can overrun the target's\n" " buffers. [default: off]\n" " -maxnowait off Do not enforce any limit on how many packets may be\n" " sent without any intermediate delay.\n" "\n" " -waitfor The number of microseconds to wait *after* 'maxnowait'\n" " packets have been sent with no gap. The default is 1000.\n" "\n" " -buffer Use a circular buffer of size +1.\n" " The default is %d.\n" "\n" " -tsinpkt How many TS packets to put in each circular buffer item\n" " (i.e., how many TS packets will end up in each UDP packet).\n" " This defaults to 7, which is the number guaranteed to fit\n" " into a single ethernet packet. Specifying more than 7 will\n" " give fragmented packets on 'traditional' networks. Specifying\n" " less will cause more packets than necessary.\n" "\n" "When the child process starts up, it waits for the circular buffer to fill\n" "up before it starts sending any data.\n" "\n" " -prime Prime the PCR timing mechanism with 'time' for\n" " circular buffer items. The default is %d\n" " -speedup Percentage of 'normal speed' to use when\n" " calculating the priming time.\n" "\n" "Unless -nopcrs is selected, packet times are calculated using PCRs,\n" "as they are found. The program starts with a number of bytes\n" "'in hand', and a corresponding time calculated using the default\n" "byterate. As data is actually output, the number of bytes output is\n" "subtracted from the total 'in hand', and the time remaining amended\n" "likewise. When a new PCR is found, the number of bytes and given\n" "number of microseconds since the last PCR is added to the 'in hand'\n" "totals.\n" "\n" "The -prime switch can be used to determine how many circular buffer\n" "items (i.e., 188*7 byte packets) should be used to prime the number\n" "of bytes and time held 'in hand'. Larger numbers will allow the\n" "program to cope with longer distances between PCRs, and will also\n" "tend to smooth out the byte rates indicated by adjacent PCRs.\n" "\n" " -pcr_scale Scale PCR values by this percentage.\n" " is a floating (double) value.\n" "\n" "If a PCR scale is given, then all PCRs will be multiplied by\n" "/100. Thus '-pcr_scale 100' will have no effect,\n" "'-pcr_scale 200' will double each PCR, and '-pcr_scale 50' will halve\n" "each PCR value.\n" "\n" " -pwait The parent process should wait ms when the\n" " buffer is full before checking again.\n" " The default is 50ms.\n" " -cwait The child processs should wait ms when the\n" " buffer is empty, before checking again.\n" " The default is 10ms.\n" "\n" "For convenience, the '-hd' switch is provided for playing HD video:\n" "\n" " -hd equivalent to '-bitrate 20000000 -maxnowait off\n" " -pwait 4 -cwait 1'\n" "\n" "(the exact values may change in future releases of this software).\n" "It may also sometimes help to specify '-nopcr' as well (i.e., ignore\n" "the timing information in the video stream itself).\n" "", DEFAULT_BYTE_RATE, DEFAULT_BYTE_RATE*8, DEFAULT_CIRCULAR_BUFFER_SIZE, DEFAULT_PRIME_SIZE); } /* * Write a usage string (to standard output) describing the testing * options processed by tswrite_process_args. */ extern void tswrite_help_testing(void) { print_msg( "Testing:\n" "In order to support some form of automatic 'jitter' in the output,\n" "the child process's idea of time can be randomly perturbed:\n" "\n" " -perturb \n" "\n" " is the initial seed for the random number generator (1 is a\n" "traditional default), and is the maximum amount to perturb\n" "time by -- this will be used in both the positive and negative\n" "directions, and is in milliseconds. is either 0 or 1 --\n" "if it is 1 then each perturbation time will be reported.\n" "It is probably worth selecting a large value for -maxnowait when\n" "using -perturb.\n" ); } /* * Write a usage string (to standard output) describing the * debugging options processed by tswrite_process_args. */ extern void tswrite_help_debug(void) { print_msg( "Debugging:\n" " -pdebug Output debugging messages for the parent process\n" " -pdebug2 Output debugging messages for the parent process\n" " (report on times intermediate between PCRs)\n" " -cdebug Output debugging messages for the child process\n" #if DISPLAY_BUFFER " -visual Output a visual representation of how the\n" " internal cicular buffer works. It is recommended\n" " that this is done with small datasets and low\n" " (e.g., 10) values for the circular buffer size\n" #endif ); } /* * Report on the values within our argument context. * * Also reports on the various global/debug values. */ extern void tswrite_report_args(TS_context_p context) { fprint_msg("Circular buffer size %d (+1)\n",context->circ_buf_size); fprint_msg("Transmitting %s%d TS packet%s (%d bytes) per network" " packet/circular buffer item\n", context->TS_in_item==1?"":"(up to) ", context->TS_in_item, context->TS_in_item==1?"":"s", context->TS_in_item*TS_PACKET_SIZE); if (context->bitrate % 1000000 == 0) fprint_msg("Requested data rate is %d Mbps ",context->bitrate/1000000); else if (context->bitrate % 1000 == 0) fprint_msg("Requested data rate is %d kbps ",context->bitrate/1000); else fprint_msg("Requested data rate is %d bps ",context->bitrate); fprint_msg("(%d bytes/second)\n",context->byterate); if (context->maxnowait == -1) print_msg("Maximum number of packets to send with no wait: No limit\n"); else { fprint_msg("Maximum number of packets to send with no wait: %d\n", context->maxnowait); fprint_msg("Number of microseconds to wait thereafter: %d\n", context->waitfor); } if (context->pcr_mode != TSWRITE_PCR_MODE_NONE) { fprint_msg("PCR mechanism 'primed' with time for %d circular buffer items\n", context->prime_size); if (context->prime_speedup != 100) fprint_msg("PCR mechanism 'prime speedup' is %d%%\n", context->prime_speedup); } else print_msg("Using requested data rate directly to time packets" " (ignoring any PCRs)\n"); if (context->pcr_scale) fprint_msg("Multiply PCRs by %g\n",context->pcr_scale); if (global_parent_wait != DEFAULT_PARENT_WAIT) fprint_msg("Parent will wait %dms for buffer to unfill\n", global_parent_wait); if (global_child_wait != DEFAULT_CHILD_WAIT) fprint_msg("Child will wait %dms for buffer to unempty\n", global_child_wait); if (global_perturb_range) { fprint_msg("Randomly perturbing child time by -%u..%ums" " with seed %u\n",global_perturb_range,global_perturb_range, global_perturb_seed); } } /* * Various command line switches that are useful for tswrite are really * only interpretable by tswrite itself. Thus we provide a function that * will process such switches. * * This function extracts appropriate switches from `argv`, and returns it * altered appropriately. * * - `prefix` is a prefix for any error messages - typically the * short name of the program running. * - `argc` and `argv` are as passed to `main`. After * this function has finished, any arguments that it has processed will have * had their `argv` array elements changed to point to the string * "" (this is defined as the string TSWRITE_PROCESSED in the * tswrite.h header file). * - values are set in `context` to indicate the user's requests, * and also any appropriate defaults. * * Note that `tswrite_print_usage` may be used to print out a description of * the switches processed by this function. * * Returns 0 if all goes well, 1 if there was an error. Note that not * specifying an output file or host counts as an error. */ extern int tswrite_process_args(char *prefix, int argc, char *argv[], TS_context_p context) { int err = 0; int ii = 1; context->circ_buf_size = DEFAULT_CIRCULAR_BUFFER_SIZE; context->TS_in_item = DEFAULT_TS_PACKETS_IN_ITEM; context->maxnowait = -1; context->waitfor = 1000; context->byterate = DEFAULT_BYTE_RATE; context->bitrate = context->byterate * 8; context->pcr_mode = TSWRITE_PCR_MODE_PCR2; context->prime_size = DEFAULT_PRIME_SIZE; context->prime_speedup = 100; context->pcr_scale = 1.0; context->pkt_hdr_type = PKT_HDR_TYPE_NONE; while (ii < argc) { if (!strcmp("-nopcrs",argv[ii])) { context->pcr_mode = TSWRITE_PCR_MODE_NONE; argv[ii] = TSWRITE_PROCESSED; } else if (!strcmp("-bitrate",argv[ii])) { CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10, &context->bitrate); if (err) return 1; context->byterate = context->bitrate / 8; argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-byterate",argv[ii])) { CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10, &context->byterate); if (err) return 1; context->bitrate = context->byterate * 8; argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-prime",argv[ii])) { CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10, &context->prime_size); if (err) return 1; if (context->prime_size < 1) { fprint_err("### %s: -prime 0 does not make sense\n",prefix); return 1; } argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-speedup",argv[ii])) { CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10, &context->prime_speedup); if (err) return 1; if (context->prime_speedup < 1) { fprint_err("### %s: -speedup 0 does not make sense\n",prefix); return 1; } argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-pcr_scale",argv[ii])) { double percentage; CHECKARG(prefix,ii); err = double_value(prefix,argv[ii],argv[ii+1],TRUE,&percentage); if (err) return 1; argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; context->pcr_scale = percentage / 100.0; fprint_msg("PCR accelerator = %g%% = PCR * %g\n",percentage,context->pcr_scale); } else if (!strcmp("-maxnowait",argv[ii])) { CHECKARG(prefix,ii); if (!strcmp(argv[ii+1],"off")) context->maxnowait = -1; else { err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10, &context->maxnowait); if (err) return 1; } argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-waitfor",argv[ii])) { CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10, &context->waitfor); if (err) return 1; argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-buffer",argv[ii])) { CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10, &context->circ_buf_size); if (err) return 1; if (context->circ_buf_size < 1) { fprint_err("### %s: -buffer 0 does not make sense\n",prefix); return 1; } argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-tsinpkt",argv[ii])) { CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10, &context->TS_in_item); if (err) return 1; if (context->TS_in_item < 1) { fprint_err("### %s: -tsinpkt 0 does not make sense\n",prefix); return 1; } else if (context->TS_in_item > MAX_TS_PACKETS_IN_ITEM) { fprint_err("### %s: -tsinpkt %d is too many (maximum is %d)\n", prefix,context->TS_in_item,MAX_TS_PACKETS_IN_ITEM); return 1; } argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-rtp", argv[ii])) { context->pkt_hdr_type = PKT_HDR_TYPE_RTP; argv[ii] = TSWRITE_PROCESSED; } else if (!strcmp("-hd", argv[ii])) { context->maxnowait = 40; context->bitrate = 20000000; context->byterate = context->bitrate / 8; global_parent_wait = 4; global_child_wait = 1; argv[ii] = TSWRITE_PROCESSED; } else if (!strcmp("-cdebug",argv[ii])) { global_child_debug = TRUE; argv[ii] = TSWRITE_PROCESSED; } else if (!strcmp("-pdebug",argv[ii])) { global_parent_debug = TRUE; argv[ii] = TSWRITE_PROCESSED; } else if (!strcmp("-pdebug2",argv[ii])) { global_parent_debug = TRUE; global_show_all_times = TRUE; argv[ii] = TSWRITE_PROCESSED; } else if (!strcmp("-pwait",argv[ii])) { int temp; CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10,&temp); if (err) return 1; if (temp == 0) { fprint_err("### %s: -pwait 0 does not make sense\n",prefix); return 1; } if (temp > 999) { fprint_err("### %s: -pwait %d (more than 999) not allowed\n", prefix,temp); return 1; } global_parent_wait = temp; argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-cwait",argv[ii])) { int temp; CHECKARG(prefix,ii); err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10,&temp); if (err) return 1; if (temp == 0) { fprint_err("### %s: -cwait 0 does not make sense\n",prefix); return 1; } if (temp > 999) { fprint_err("### %s: -cwait %d (more than 999) not allowed\n", prefix,temp); return 1; } global_child_wait = temp; argv[ii] = argv[ii+1] = TSWRITE_PROCESSED; ii++; } else if (!strcmp("-perturb",argv[ii])) { int temp; if (ii+3 >= argc) { fprint_err("### %s: -perturb should have three arguments: " " \n",prefix); return 1; } err = int_value(prefix,argv[ii],argv[ii+1],TRUE,10,&temp); if (err) return 1; global_perturb_seed = temp; err = int_value(prefix,argv[ii],argv[ii+2],TRUE,10,&temp); if (err) return 1; if (temp == 0) { fprint_err("### %s: a range of 0 for -perturb does not make sense\n",prefix); return 1; } global_perturb_range = temp; if (strlen(argv[ii+3]) != 1) { fprint_err("### %s: the flag for -perturb must be 0 or 1," " not '%s'\n",prefix,argv[ii+3]); return 1; } switch (argv[ii+3][0]) { case '0': global_perturb_verbose = FALSE; break; case '1': global_perturb_verbose = TRUE; break; default: fprint_err("### %s: the flag for -perturb must be 0 or 1," "not '%c'\n",prefix,argv[ii+3][0]); return 1; } argv[ii] = argv[ii+1] = argv[ii+2] = argv[ii+3] = TSWRITE_PROCESSED; ii+=3; } #if DISPLAY_BUFFER else if (!strcmp("-visual",argv[ii])) { global_show_circular = TRUE; argv[ii] = TSWRITE_PROCESSED; } #endif ii++; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tswrite_defns.h000066400000000000000000000207031261471605300177230ustar00rootroot00000000000000/* * Support for writing out TS packets, to file, or over TCP/IP or UDP * * When writing asynchronously, provides automated producer/consumer * behaviour via a circular buffer, optionally taking timing from the * TS PCR entries. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _tswrite_defns #define _tswrite_defns #include "compat.h" #include "ts_defns.h" #include "h222_defns.h" #ifdef _WIN32 #include // for definition of SOCKET #else typedef int SOCKET; // for compatibility with Windows #include // for struct termios #endif struct buffered_TS_output; typedef struct buffered_TS_output *buffered_TS_output_p; #define SIZEOF_BUFFERED_TS_OUTPUT sizeof(struct buffered_TS_output) // ============================================================ // EXTERNAL DATASTRUCTURES - these are *intended* for external use // ============================================================ // Our supported target types // On Unix-type systems, there is little distinction between file and // socket, but on Windows this becomes more interesting enum TS_writer_type { TS_W_UNDEFINED, TS_W_STDOUT, // standard output TS_W_FILE, // a file TS_W_TCP, // a socket, over TCP/IP TS_W_UDP, // a socket, over UDP }; typedef enum TS_writer_type TS_WRITER_TYPE; // ------------------------------------------------------------ // So, *is* it a file or a socket? union TS_writer_output { FILE *file; SOCKET socket; }; // ------------------------------------------------------------ // A datastructure to allow us to write to various different types of target // // When writing to a file, "how" will be TS_W_STDOUT or TS_W_FILE, and // "where" will be the appropriate file interface. "writer" is not necessary // (there's no point in putting a circular buffer and other stuff above // the file writes), and no child process is needed. // // When writing over UDP, "how" will be TS_W_UDP, and "where" will be the // socket that is being written to. For UDP, timing needs to be managed, and // thus the circular buffer support is necessary, so "writer" should be // set to a buffered output context. Since the circular buffer is being // used, there will also be a child process. // // When writing over TCP/IP, "how" will be TS_W_TCP, and "where" will be the // socket that is being written to. Timing is not an issue, so "writer" will // not be needed, and nor will there be a child process. However, it is // possible that we will want to respond to commands (over the same or another // socket (or, on Linux/BSD, file descriptor)), so "commander" may be set. struct TS_writer { enum TS_writer_type how; // what type of output we want union TS_writer_output where; // where it's going to buffered_TS_output_p writer; // our buffered output interface, if needed int count; // a count of how many TS packets written // Support for the child fork/thread, which actually does the writing when // buffered output is enabled. #ifdef _WIN32 HANDLE child; // the handle for the child thread (if any) #else // _WIN32 pid_t child; // the PID of the child process (if any) #endif // _WIN32 int quiet; // Should the child be as quiet as possible? // Support for "commands" being sent to us via a socket (or, on Linux/BSD, // from any other file descriptor). The "normal" way this is used is for // our application (tsserve) to act as a server, listening on a socket // for an incoming connection, and then both playing data to that // connection, and listening for commands from it. int server; // are we acting as a server? SOCKET command_socket; // where to read commands from/through // When the user sends a new command (a different character than is // currently in `command`), the underpinnings of tswrite_write() set // `command` to that command letter, and `command_changed` to TRUE. // Various key functions that write to TS check `command_changed`, and // return COMMAND_RETURN_CODE if it is true. // Note, however, that it is left up to the top level to *unset* // `command_changed` again. byte command; // A single character "command" for what to do int command_changed; // Has it changed? // Some commands (notably, the "skip" commands) want to be atomic - that // is, they should not be interrupted by the user "typing ahead". Since // the fast forward and reverse mechanisms (used for skipping as well) // call tswrite_command_changed() to tell if there is a new command that // should interrup them, we can provide a flag to say "don't do that"... int atomic_command; // Should some TS packets be thrown away every packets? This can be // useful for debugging other applications int drop_packets; // 0 to keep all packets, otherwise keep packets int drop_number; // and then drop this many }; typedef struct TS_writer *TS_writer_p; #define SIZEOF_TS_WRITER sizeof(struct TS_writer) // ------------------------------------------------------------ // Command letters #define COMMAND_NOT_A_COMMAND '_' // A guaranteed non-command letter #define COMMAND_QUIT 'q' // quit/exit #define COMMAND_NORMAL 'n' // normal playing speed #define COMMAND_PAUSE 'p' // pause until another command #define COMMAND_FAST 'f' // fast forward #define COMMAND_FAST_FAST 'F' // faster forward #define COMMAND_REVERSE 'r' // reverse/rewind #define COMMAND_FAST_REVERSE 'R' // faster reverse/rewind #define COMMAND_SKIP_FORWARD '>' // aim at 10s #define COMMAND_SKIP_BACKWARD '<' // ditto #define COMMAND_SKIP_FORWARD_LOTS ']' // aim at 100s #define COMMAND_SKIP_BACKWARD_LOTS '[' // ditto #define COMMAND_SELECT_FILE_0 '0' #define COMMAND_SELECT_FILE_1 '1' #define COMMAND_SELECT_FILE_2 '2' #define COMMAND_SELECT_FILE_3 '3' #define COMMAND_SELECT_FILE_4 '4' #define COMMAND_SELECT_FILE_5 '5' #define COMMAND_SELECT_FILE_6 '6' #define COMMAND_SELECT_FILE_7 '7' #define COMMAND_SELECT_FILE_8 '8' #define COMMAND_SELECT_FILE_9 '9' // And a "return code" that means "the command character has changed" #define COMMAND_RETURN_CODE -999 typedef enum tswrite_pcr_mode_e { TSWRITE_PCR_MODE_NONE, TSWRITE_PCR_MODE_PCR1, TSWRITE_PCR_MODE_PCR2 } tswrite_pcr_mode; typedef enum tswrite_pkt_hdr_type_e { PKT_HDR_TYPE_NONE = 0, PKT_HDR_TYPE_RTP } tswrite_pkt_hdr_type_t; // ------------------------------------------------------------ // Context for use in decoding command line - see `tswrite_process_args()` struct TS_context { // Values used in setting up buffered output int circ_buf_size; // number of buffer entries (+1) for circular buffer int TS_in_item; // number of TS packets in each circular buffer item int maxnowait; // max number of packets to send without waiting int waitfor; // the number of microseconds to wait thereafter int bitrate; // suggested bit rate (byterate*8) - both are given int byterate; // suggested byte rate (bitrate/8) - for convenience tswrite_pcr_mode pcr_mode; // use PCRs for timing information? int prime_size; // initial priming size for buffered output int prime_speedup; // percentage of normal speed to prime with tswrite_pkt_hdr_type_t pkt_hdr_type; double pcr_scale; // multiplier for PCRs -- see buffered_TS_output }; typedef struct TS_context *TS_context_p; // Arguments processed by tswrite_process_args are set to: #define TSWRITE_PROCESSED "" #endif // _tswrite_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/tswrite_fns.h000066400000000000000000000342451261471605300174200ustar00rootroot00000000000000/* * Support for writing out TS packets, to file, or over TCP/IP or UDP * * When writing asynchronously, provides automated producer/consumer * behaviour via a circular buffer, optionally taking timing from the * TS PCR entries. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _tswrite_fns #define _tswrite_fns #include "tswrite_defns.h" /* * Open a file for TS output. * * - `how` is how to open the file or connect to the host * - `name` is the name of the file or host to open/connect to * (this is ignored if `how` is TS_W_STDOUT) * - if `how` is TS_W_UDP, and `name` is a multicast address, * then `multicast_if` may be the IP address of the network * address to use, or NULL if the default interface should * be used. * - if it is a socket (i.e., if `how` is TS_W_TCP or TS_W_UDP), * then `port` is the port to use, otherwise this is ignored * - `quiet` is true if only error messages should be printed * - `tswriter` is the new context to use for writing TS output, * which should be closed using `tswrite_close`. * * For TS_W_STDOUT, there is no need to open anything. * * For TS_W_FILE, ``open(name,O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,00777)`` * is used - i.e., the file is opened so that anyone may read/write/execute * it. If ``O_BINARY`` is not defined (e.g., on Linux), then it is * omitted. * * For TS_W_TCP and TS_W_UDP, the ``connect_socket`` function is called, * which uses ``socket`` and ``connect``. * * In all cases (even when using TS_W_STDOUT), the `tswriter` should be * closed using `tswrite_stdout`. * * For TS_W_UDP, the ``tswrite_startt_buffering`` function must be called * before any output is written via the `tswriter`. For other forms of output, * this is optional. * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_open(TS_WRITER_TYPE how, char *name, char *multicast_if, int port, int quiet, TS_writer_p *tswriter); /* * Open a network connection for TS output. * * This is a convenience wrapper around `tswrite_open`. * * - `name` is the name of the host to connect to * - `port` is the port to connect to * - `use_tcp` is TRUE if TCP/IP should be use, FALSE if UDP should be used * - `quiet` is true if only error messages should be printed * - `tswriter` is the new context to use for writing TS output, * which should be closed using `tswrite_close`. * * In all cases, the `tswriter` should be closed using `tswrite_stdout`. * * For TS_W_UDP, the ``tswrite_start_buffering`` function must be called * before any output is written via the `tswriter`. For other forms of output, * this is optional. * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_open_connection(int use_tcp, char *name, int port, int quiet, TS_writer_p *tswriter); /* * Open a file for TS output. * * This is a convenience wrapper around `tswrite_open`. * * - `name` is the name of the file to open, or NULL if stdout should be used * - `quiet` is true if only error messages should be printed * - `tswriter` is the new context to use for writing TS output, * which should be closed using `tswrite_close`. * * In all cases (even when using TS_W_STDOUT), the `tswriter` should be * closed using `tswrite_stdout`. * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_open_file(char *name, int quiet, TS_writer_p *tswriter); /* * Wait for a client to connect and then both write TS data to it and * listen for command from it. Uses TCP/IP. * * - `server_socket` is the socket on which we will listen for a connection * - `quiet` is true if only error messages should be printed * - `tswriter` is the new context to use for writing TS output, * which should be closed using `tswrite_close`. * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_wait_for_client(int server_socket, int quiet, TS_writer_p *tswriter); /* * Set up internal buffering for TS output. This is necessary for UDP * output, and optional otherwise. * * Builds the internal circular buffer and other datastructures, and * forks a child proces to send data over the socket. * * (This is *intended* for use when outputting via a socket, but there * is nothing actually stopping it from being used to output to a file. * This is unlikely to be useful for other than testing purposes, however.) * * See also `tswrite_start_buffering_from_context`, which uses the `context` * datastructure that is prepared by `tswrite_process_args`. * * - `tswriter` is the TS output context returned by `tswrite_open` * - `circ_buf_size` is the number of buffer entries (plus one) we would * like in the underlying circular buffer. * - `TS_in_packet` is the number of TS packets to allow in each network * packet. * - `maxnowait` is the maximum number of packets to send to the target * host with no wait between packets * - `waitfor` is the number of microseconds to wait for thereafter * - `byterate` is the (initial) rate at which we'd like to output our data * - `use_pcrs` is TRUE if PCRs in the data stream are to be used for * timing output (the normal case), otherwise the specified byte rate * will be used directly. * - `prime_size` is how much to prime the circular buffer output timer * - `prime_speedup` is the percentage of "normal speed" to use for the priming * rate. This should normally be set to 100 (i.e., no effect). * - `pcr_scale` determines how much to "accelerate" each PCR - see the * notes elsewhere on how this works. * * Returns 0 if all went well, 1 if something went wrong. */ extern int tswrite_start_buffering(TS_writer_p tswriter, int circ_buf_size, int TS_in_packet, int maxnowait, int waitfor, int byterate, tswrite_pcr_mode pcr_mode, int prime_size, int prime_speedup, double pcr_scale, const tswrite_pkt_hdr_type_t hdr_type); /* * Set up internal buffering for TS output. This is necessary for UDP output, * and optional otherwise. * * This alternative takes the `context` datastructure that is prepared * by `tswrite_process_args`. * * - `tswriter` is the TS output context returned by `tswrite_open` * - `context` contains the necessary information, as given by the user * * Returns 0 if all went well, 1 if something went wrong. */ extern int tswrite_start_buffering_from_context(TS_writer_p tswriter, TS_context_p context); /* * Indicate to a TS output context that `input` is to be used as * command input. * * This function may only be used if output is via TCP/IP. * * - `tswriter` is the TS output context returned by `tswrite_open` * - `input` is the socket (or, on Linux/BSD, file descriptor) on which * to listen for commands. * * Note that this should either be ``tswriter->where.socket`` or * STDIN_FILENO - no other values are currently supported (particularly * since no attempt is made to close this socket when things are finished, * which doesn't matter for the given values). * * This function: * * - makes the socket on which data will be written non-blocking * (i.e., if the socket is not ready to be written to, it will not * accept input and block until it can be used, which means that it * becomes our responsibility to ask if the socket is ready for output) * - makes tswrite_write "look" on the `input` to see if a (single * character) command has been given, and if it has, put it into * the `tswriter` datastructure for use * * The command state is set to 'p'ause - i.e., as if the client had sent * a COMMAND_PAUSE command. * * Returns 0 if all went well, 1 if something went wrong. */ extern int tswrite_start_input(TS_writer_p tswriter, SOCKET input); /* * Set/unset "atomic" status - i.e., whether a command may be interrupted * by the next command. * * Most commands (normal play, fast forwards, etc.) should be interrupted * by a new command. However, some (the skip forwards and backwards commands) * make sense only if they will always complete. This function allows that * state to be toggled. */ extern void tswrite_set_command_atomic(TS_writer_p tswriter, int atomic); /* * Ask a TS writer if changed input is available. * * If the TS writer is enabled for command input, then if the command * currently being executed has declared itself "atomic" (i.e., not able to be * interrupted), it returns FALSE, otherwise it returns TRUE if the command * character has changed. */ extern int tswrite_command_changed(TS_writer_p tswriter); /* * Close a file or socket opened using `tswrite_open`, and if necessary, * send the child process used for output buffering an end-of-file * indicator, and wait for it to finish. * * - `tswriter` is the TS output context returned by `tswrite_open` * - if `quiet` is true, then waiting for the child to exit should * not be reported on (i.e., only errors should produce output) * * Returns 0 if all goes well, 1 if something went wrong. */ extern int tswrite_close(TS_writer_p tswriter, int quiet); /* * Wait for a new command after 'p'ausing. * * - `tswriter` is the TS output context returned by `tswrite_open` * * Returns 0 if all went well, 1 if something went wrong. */ extern int wait_for_command(TS_writer_p tswriter); /* * Write a Transport Stream packet out via the TS writer. * * - `tswriter` is the TS output context returned by `tswrite_open` * - `packet` is the TS packet * - if the packets payload_unit_start_indicator is set, then * `pid` is the PID for this packet, `got_pcr` is TRUE if it * contains a PCR in its adaptation field, and `pcr` contains * said PCR. These values are only used when outputting via * buffered output. * * Returns 0 if all goes well, 1 if something went wrong, and EOF if command * input is enabled (only allowed for TCP/IP output) and the 'q'uit command * has been given (in which case, no further commands will be read, and no * more output will be written, by any subsequent calls of this function). */ extern int tswrite_write(TS_writer_p tswriter, byte packet[TS_PACKET_SIZE], uint32_t pid, int got_pcr, uint64_t pcr); extern int tswrite_discontinuity(const TS_writer_p tswriter); /* * Write a usage string (to standard output) describing the tuning * options processed by tswrite_process_args. */ extern void tswrite_help_tuning(void); /* * Write a usage string (to standard output) describing the testing * options processed by tswrite_process_args. */ extern void tswrite_help_testing(void); /* * Write a usage string (to standard output) describing the * debugging options processed by tswrite_process_args. */ extern void tswrite_help_debug(void); /* * Report on the values within our argument context. * * Also reports on the various global/debug values. * * Note that it is up to the caller to ensure that they *use* all * the values reported on here! */ extern void tswrite_report_args(TS_context_p context); /* * Various command line switches that are useful for tswrite are really * only interpretable by tswrite itself. Thus we provide a function that * will process such switches. * * This function extracts appropriate switches from `argv`, and returns it * altered appropriately. * * - `prefix` is a prefix for any error messages - typically the * short name of the program running. * - `argc` and `argv` are as passed to `main`. After * this function has finished, any arguments that it has processed will have * had their `argv` array elements changed to point to the string * "" (this is defined as the string TSWRITE_PROCESSED in the * tswrite.h header file). * - values are set in `context` to indicate the user's requests, * and also any appropriate defaults. * * Note that `tswrite_print_usage` may be used to print out a description of * the switches processed by this function. * * Returns 0 if all goes well, 1 if there was an error. Note that not * specifying an output file or host counts as an error. */ extern int tswrite_process_args(char *prefix, int argc, char *argv[], TS_context_p context); #endif // _tswrite_fns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/version.h000066400000000000000000000032261261471605300165310ustar00rootroot00000000000000/* * The official version number of this set of software. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _version #define _version #include "printing_fns.h" #define STRINGIZE1(x) #x #define STRINGIZE(x) STRINGIZE1(x) #define TSTOOLS_VERSION_STRING STRINGIZE(TSTOOLS_VERSION) const char software_version[] = TSTOOLS_VERSION_STRING; // The following is intended to be output as part of the main help text for // each program. ``program_name`` is thus the name of the program. #define REPORT_VERSION(program_name) \ fprint_msg(" TS tools version %s, %s built %s %s\n", \ software_version,(program_name), \ __DATE__,__TIME__) #endif // _version // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/video_defns.h000066400000000000000000000030431261471605300173260ustar00rootroot00000000000000/* * Support for generic video streams * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #ifndef _video_defns #define _video_defns #include "h222_defns.h" // Recognised types of video input // These are convenience names, defined in terms of the H222 values #define VIDEO_UNKNOWN 0 // which is a reserved value #define VIDEO_H262 MPEG2_VIDEO_STREAM_TYPE #define VIDEO_H264 AVC_VIDEO_STREAM_TYPE #define VIDEO_AVS AVS_VIDEO_STREAM_TYPE #define VIDEO_MPEG4_PART2 MPEG4_PART2_VIDEO_STREAM_TYPE #endif // _video_defns // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: tstools-1.13~git20151030/yuv2/000077500000000000000000000000001261471605300155755ustar00rootroot00000000000000tstools-1.13~git20151030/yuv2/Makefile000066400000000000000000000014671261471605300172450ustar00rootroot00000000000000# # Set JAVA_HOME to the location of your Java installation. # Set SWTDIR to the location which contains your swt.jar. # # make will then build a yuv2.jar . # # bin/ contains a script wrapper you can use to invoke your # yuv2.jar . Edit bin/yuv2 to give the location of your yuv2.jar. # SRC_DIR=src/com/aminocom/yuv2 SRC_FILES=AboutBox.java BufferedImage.java FormatDialog.java \ ImageCanvas.java LogWindow.java MainFrame.java Prefs.java \ Utils.java YUV2.java LOCATED_SRC=$(SRC_FILES:%.java=$(SRC_DIR)/%.java) all: if [ ! -d obj ]; then mkdir obj; fi $$JAVA_HOME/bin/javac -cp $$SWTDIR/swt.jar -d obj -sourcepath bin $(LOCATED_SRC) (cd obj; $$JAVA_HOME/bin/jar -xf $$SWTDIR/swt.jar) (cd obj; $$JAVA_HOME/bin/jar -cmf ../src/Manifest-Extras.txt ../yuv2.jar *) clean: rm -rf obj rm -f yuv2.jar # End file. tstools-1.13~git20151030/yuv2/README000066400000000000000000000023131261471605300164540ustar00rootroot00000000000000YUV2: A YUV viewer. ------------------ YUV2 is a cross-platform Java YUV file viewer intended for video codec engineers. It includes pseudo-video playback, macroblock and pixel value viewers for both frame and field mode. It'll get extended as we need more features - do feel free to jump in and help. This initial release is for Linux only (because that's what I've got on my desktop). To get things working on Windows you will need to translate the GNU makefile into a Windows NMAKE file or (more likely) a batch script and use the Windows SWT. You will need SWT, available from . This code was originally written for SJ Consulting and originally released by Amino Communications Ltd in March 2008 - I've only just got around to tidying it up enough that it deserves a public release. TO BUILD YUV2 ------------- Set JAVA_HOME to where your Java is - e.g. /usr/local/jdk1.6.0_07 . Set SWTDIR to wherever your swt.jar is. Type 'make'. Copy yuv2.jar to somewhere convenient (default is /usr/local/lib) Edit bin/yuv2 to reflect the directory where you put yuv2. Copy bin/yuv2 to somewhere on your PATH. Report (or better still, fix) the bugs! Richard Watts 2008-09-15tstools-1.13~git20151030/yuv2/bin/000077500000000000000000000000001261471605300163455ustar00rootroot00000000000000tstools-1.13~git20151030/yuv2/bin/yuv2000077500000000000000000000001101261471605300171700ustar00rootroot00000000000000#! /bin/sh YUV2DIR=/usr/local/lib exec java -jar $YUV2DIR/yuv2.jar $* tstools-1.13~git20151030/yuv2/src/000077500000000000000000000000001261471605300163645ustar00rootroot00000000000000tstools-1.13~git20151030/yuv2/src/Manifest-Extras.txt000066400000000000000000000000431261471605300221340ustar00rootroot00000000000000Main-Class: com.aminocom.yuv2.YUV2 tstools-1.13~git20151030/yuv2/src/com/000077500000000000000000000000001261471605300171425ustar00rootroot00000000000000tstools-1.13~git20151030/yuv2/src/com/aminocom/000077500000000000000000000000001261471605300207445ustar00rootroot00000000000000tstools-1.13~git20151030/yuv2/src/com/aminocom/yuv2/000077500000000000000000000000001261471605300216515ustar00rootroot00000000000000tstools-1.13~git20151030/yuv2/src/com/aminocom/yuv2/AboutBox.java000077500000000000000000000062431261471605300242470ustar00rootroot00000000000000/* * Code for the About box. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * Kynesim, Cambridge UK * * ***** END LICENSE BLOCK ***** */ package com.aminocom.yuv2; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; public class AboutBox { Shell mShell; public static final String sCommentText = "Pre-release version. Please report bugs to \n" + " "; public AboutBox(Shell inParent) { mShell = new Shell(inParent); mShell.setLayout(new FormLayout()); mShell.setText("About YUV2"); Composite topPanel = new Composite(mShell, SWT.NONE); Composite bottomPanel = new Composite(mShell, SWT.NONE); Label about = new Label(topPanel, SWT.NONE); about.setText("YUV2 0.3 (c) SJ Consulting 2006"); StyledText mainText = new StyledText(topPanel, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); mainText.setText(sCommentText); { RowLayout rl = new RowLayout(); rl.fill = true; rl.type = SWT.VERTICAL; topPanel.setLayout(rl); } { RowLayout rl = new RowLayout(); bottomPanel.setLayout(rl); } Button ok = new Button(bottomPanel, SWT.NONE); ok.setText("Dismiss"); ok.addListener(SWT.Selection, new Listener() { public void handleEvent(Event ev) { AboutBox.this.close(); } }); topPanel.setLayoutData(Utils.makeFormData (new FormAttachment(0), new FormAttachment(100), new FormAttachment(0), new FormAttachment(bottomPanel))); bottomPanel.setLayoutData(Utils.makeFormData (new FormAttachment(0), new FormAttachment(100), null, new FormAttachment(100))); mShell.pack(); } public void show() { mShell.open(); while (!mShell.isDisposed()) { if (!mShell.getDisplay().readAndDispatch()) { mShell.getDisplay().sleep(); } } } public void close() { mShell.close(); } public boolean isDisposed() { return mShell.isDisposed(); } } tstools-1.13~git20151030/yuv2/src/com/aminocom/yuv2/BufferedImage.java000077500000000000000000000166401261471605300252130ustar00rootroot00000000000000/* * Display an image, performing colourspace conversion as we do so. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * Kynesim, Cambridge UK * * ***** END LICENSE BLOCK ***** */ package com.aminocom.yuv2; import java.io.RandomAccessFile; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; /** This class buffers a frame from a file in the hope that * we can make animation a bit smoother. * * @author rrw * */ public class BufferedImage { ImageCanvas mParentCanvas; MainFrame mContainingFrame; int mFrameNumber; boolean mReadyForDrawing; /** The image data to paint. This is updated * whenever we do anything by updateImageData(). * * When we have no image data, we set this to NULL * and paint plain white. */ ImageData mImageData; /** The image to paint, if present. */ Image mImage; /** Y frame buffer. Used in image conversion and * the dropper tool. * Frame buffers are stored in ordinary raster format. */ byte[] mYBuf, mUBuf, mVBuf; BufferedImage(int inFrameNumber, ImageCanvas inParent) { mParentCanvas = inParent; mContainingFrame = inParent.mContainingFrame; mFrameNumber = inFrameNumber; mReadyForDrawing = false; } void dispose() { if (mImage != null) { mImage.dispose(); } mImage = null; mImageData = null; } boolean convertImage(boolean inQuiet) { RandomAccessFile reader = mParentCanvas.getReader(); if (reader == null) { // No file .. return false; } // Start off by killing any old data .. // We actually do want to do this here - the last thing the // user wants is to navigate to a frame and have us redisplay // the last one they visited. if (mImage != null) { mImage.dispose(); } mImageData = null; mImage = null; if (mParentCanvas.mWidth == 0 || mParentCanvas.mHeight == 0) { // Bugger. No dimensions. Have to wait // until we have some. mContainingFrame.updateStatus(); mParentCanvas.closeReader(reader); return false; } // frame size = width * height for luma, half that for chroma. long frameSize = (mParentCanvas.mWidth * mParentCanvas.mHeight * 3)>>1; long fileOffset = frameSize*((long)mFrameNumber); if (!inQuiet) { mContainingFrame.log("Frame Size = 0x" + Long.toString(frameSize) + " file offset for frame " + Integer.toString(mFrameNumber) + " = 0x" + Long.toString(fileOffset, 16)); } try { long fileLen = reader.length(); mParentCanvas.mFrameTotal = fileLen/frameSize; mContainingFrame.setNumFrames (mParentCanvas.mFrameTotal); if (fileOffset > fileLen) { // Can't display this frame. mContainingFrame.note("Attempt to read frame past end of file: " + "frame " + Integer.toString(mFrameNumber) + "@" + Long.toString(fileOffset) + " > file len " + Long.toString(fileLen)); mContainingFrame.updateStatus(); mParentCanvas.closeReader(reader); return false; } if (fileOffset + frameSize > fileLen) { mContainingFrame.note("File ends between frame " + Integer.toString(mFrameNumber) + " and " + Integer.toString(mFrameNumber+1)); } reader.seek(fileOffset); } catch (Exception e) { mContainingFrame.updateStatus(); Utils.showMessageBox(mParentCanvas.mShell, "Can't read frame from input file."); } // Right. Read as much data as we can into the // relevant buffers. Our YUV file format is // Y, then U, then V, in separate chunks. int array_height = mParentCanvas.mHeight; // Round the array size up to a multiple of two lines so // our conversion routine will run at a sensible speed. if (array_height%1 != 0) { ++array_height; } mYBuf = new byte[mParentCanvas.mWidth * array_height]; mUBuf = new byte[(mParentCanvas.mWidth>>1)* (array_height>>1)]; mVBuf = new byte[(mParentCanvas.mWidth>>1) * (array_height>>1)]; try { reader.readFully(mYBuf); reader.readFully(mUBuf); reader.readFully(mVBuf); } catch (Exception e) { mContainingFrame.note("Didn't get whole frame (" + e.getMessage() + ")"); } // Now convert to RGB ... // Internally, we'll use standard RGBA, so // Red = 0xff, Green = 0xff00, blue = 0xff0000 mImageData = new ImageData (mParentCanvas.mWidth, mParentCanvas.mHeight, 32, new PaletteData(0xff0000, 0xff00, 0xff)); // We'll convert 2 lines to make copying somewhat // quicker (2 lines means we can cache the chroma). int[] outgoingLine = new int[2*mParentCanvas.mWidth]; // The input is just a standard, line-by-line // 4:2:0 chroma file. if (!inQuiet) { mContainingFrame.transientInfo("Converting YUV image " + Integer.toString(mParentCanvas.mWidth) + "x" + Integer.toString(mParentCanvas.mHeight) + "..."); } for (int i=0;i>1)*(mParentCanvas.mWidth>>1); for (int j=0;j>1)]&0xff; int v = mVBuf[uvpos + (j>>1)]&0xff; int r, g, b; int ri, gi, bi; int C, D, E; C = y0 - 16; D = u- 128; E = v - 128; r = ((298 * C + 409 * E + 128)>>8); g = ((298 * C - 100 * D - 208 * E + 128) >> 8); b = ((298 * C + 516 * D + 128)>>8); if (r < 0) { ri = 0; } else if (r > 255) { ri = 255; } else ri = r; if (g < 0) { gi = 0; } else if (g > 255) { gi = 255; } else gi = g; if (b < 0) { bi = 0; } else if (b > 255) { bi = 255; } else bi = b; outgoingLine[j] = (ri << 16) | (gi << 8) | bi; C = y1 - 16; r = ((298 * C + 409 * E + 128)>>8); g = ((298 * C - 100 * D - 208 * E + 128) >> 8); b = ((298 * C + 516 * D + 128)>>8); if (r < 0) { ri = 0; } else if (r > 255) { ri = 255; } else ri = r; if (g < 0) { gi = 0; } else if (g > 255) { gi = 255; } else gi = g; if (b < 0) { bi = 0; } else if (b > 255) { bi = 255; } else bi = b; outgoingLine[mParentCanvas.mWidth + j] = (ri << 16) | (gi << 8) | bi; } mImageData.setPixels(0, i, mParentCanvas.mWidth*2, outgoingLine, 0); } if (!inQuiet) { mContainingFrame.transientInfo("Done Conversion."); } // Now we've built the ImageData, build the image .. mImage = new Image(null, mImageData); synchronized (this) { mReadyForDrawing = true; } mParentCanvas.closeReader(reader); return true; } } tstools-1.13~git20151030/yuv2/src/com/aminocom/yuv2/FormatDialog.java000077500000000000000000000142671261471605300251010ustar00rootroot00000000000000/* * The image format dialogue. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * Kynesim, Cambridge UK * * ***** END LICENSE BLOCK ***** */ package com.aminocom.yuv2; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; /** This class wraps the format and type dialog. * Create one, then call show() on it to show the dialog * and modify the relevant values. * * @author rrw * */ public class FormatDialog { Shell mDialogShell; Shell mMessageShell; int mWidth; int mHeight; int mFormat; boolean mIsOK; Combo mDimensionCombo; public final String[] sStandardDimensions = { "480x480", "720x480" }; FormatDialog(Shell parentShell, int inWidth, int inHeight, int inFormat) { mMessageShell = parentShell; mDialogShell = new Shell(parentShell, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); mDialogShell.setText("Select image dimensions"); mDialogShell.setSize(300, 200); mWidth = inWidth; mHeight = inHeight; mFormat = inFormat; Group fileSize = new Group(mDialogShell, SWT.NONE); fileSize.setText("Dimensions"); // Image dimensions thingy .. { Label aLabel; Composite hRow = new Composite(fileSize, SWT.NONE); fileSize.setLayout(new FormLayout()); hRow.setLayout(new RowLayout()); hRow.setLayoutData(Utils.makeFormData (new FormAttachment(20), new FormAttachment(100), new FormAttachment(20), new FormAttachment(100))); aLabel = new Label(hRow, SWT.NONE); aLabel.setText("Image Size:"); mDimensionCombo = new Combo(hRow, SWT.NONE); mDimensionCombo.setItems(sStandardDimensions); if (mWidth > 0 && mHeight > 0) { mDimensionCombo.add (Integer.toString(mWidth) + "x" + Integer.toString(mHeight), 0); mDimensionCombo.select (0); } } Group fileFormat = new Group(mDialogShell, SWT.NONE); fileFormat.setText("File Type"); // Image format. { Composite hCol = new Composite(fileFormat, SWT.NONE); RowLayout theLayout = new RowLayout(); theLayout.type = SWT.VERTICAL; hCol.setLayout(theLayout); { Button aButton = new Button(hCol, SWT.RADIO | SWT.RIGHT); aButton.setText("YUV"); aButton.setSelection(mFormat == 0 ? true : false); } fileFormat.setLayout(new FormLayout()); hCol.setLayoutData(Utils.makeFormData (new FormAttachment(20), new FormAttachment(100), new FormAttachment(20), new FormAttachment(100))); } Composite buttons = new Composite(mDialogShell, SWT.NONE); buttons.setLayout(new RowLayout()); Button ok = new Button(buttons, SWT.PUSH); ok.setText("Ok"); mDialogShell.setDefaultButton(ok); ok.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { boolean parsedOK = parseDialog(); if (parsedOK) { FormatDialog.this.mIsOK = true; FormatDialog.this.mDialogShell.close(); } } }); Button cancel = new Button(buttons, SWT.PUSH); cancel.setText("Cancel"); cancel.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { FormatDialog.this.mIsOK = false; FormatDialog.this.mDialogShell.close(); } }); mDialogShell.setLayout(new FormLayout()); fileSize.setLayoutData(Utils.makeFormData (new FormAttachment(0), new FormAttachment(fileFormat), new FormAttachment(0), new FormAttachment(buttons))); fileFormat.setLayoutData(Utils.makeFormData (null, new FormAttachment(100), new FormAttachment(0), new FormAttachment(buttons))); buttons.setLayoutData(Utils.makeFormData (new FormAttachment(0), new FormAttachment(100), null, new FormAttachment(100))); // Add the size and type to the group mDialogShell.open(); } /** Try to parse the dialog box. If we succeed, * return true. If we don't, pop up a message * box and return false. */ boolean parseDialog() { boolean parsedOK; String dimensionString = mDimensionCombo.getText(); String s = mDimensionCombo.getText(); Point results = new Point(mWidth,mHeight); parsedOK = Utils.parseDimensions(s, results); if (!parsedOK) { Utils.showMessageBox(mMessageShell, "Invalid dimensions - " + dimensionString + " - please try again"); } else { mWidth = results.x; mHeight = results.y; } return parsedOK; } boolean show() { mDialogShell.open(); while (!mDialogShell.isDisposed()) { if (!mDialogShell.getDisplay().readAndDispatch()) { mDialogShell.getDisplay().sleep(); } } return mIsOK; } } /* End File */ tstools-1.13~git20151030/yuv2/src/com/aminocom/yuv2/ImageCanvas.java000077500000000000000000000523611261471605300247040ustar00rootroot00000000000000/* * A canvas for painting BufferedImages on. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * Kynesim, Cambridge UK * * ***** END LICENSE BLOCK ***** */ package com.aminocom.yuv2; import java.io.File; import java.io.RandomAccessFile; import java.util.Formatter; import java.util.Locale; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; public class ImageCanvas extends Canvas { Color mBg, mWhite; Shell mShell; int mWidth, mHeight; int mFormat; int mFrameNumber; long mFrameTotal; int mZoomNumerator, mZoomDenominator; boolean mIsFieldMode; boolean mMBMarkers; RandomAccessFile mReader; public String mFilename; MainFrame mContainingFrame; int mMacroblockInfoCounter; boolean mPlayMode; int mPlayFrame; int mPlayTo; BufferedImage mCurrentImage; BufferedImage mNextImage; boolean mNextImagePending; static class PlayThread extends Thread { ImageCanvas mCanvas; public PlayThread(ImageCanvas inCanvas) { mCanvas = inCanvas; } public void run() { while (true) { int sleepTime = 40; // Inter-frame time. synchronized (mCanvas) { if (mCanvas.mKillAnimationThread) { return; } if (mCanvas.mPlayMode && (mCanvas.mFrameNumber < mCanvas.mFrameTotal && (mCanvas.mPlayTo == -1 || mCanvas.mFrameNumber < mCanvas.mPlayTo))) { boolean done_something = false; // We're not allowed to do this directly, so. if (!mCanvas.mAdvancePending && !mCanvas.mNextImagePending) { YUV2.addWork(new YUV2.WorkRequest (mCanvas.mContainingFrame, YUV2.WorkRequest.ADVANCE_PLAY)); mCanvas.mAdvancePending = true; done_something = true; } if (!mCanvas.mNextImagePending) { mCanvas.mNextImagePending = true; YUV2.addWork(new YUV2.WorkRequest (mCanvas, YUV2.WorkRequest.BUFFER_NEXT_IMAGE)); done_something = true; } if (!done_something) { // We wanted to draw, but couldn't. Check // again soon! sleepTime = 4; } } else { // We're stopped. Wait until we're turned on again. YUV2.addWork(new YUV2.WorkRequest (mCanvas.mContainingFrame, YUV2.WorkRequest.STOP_PLAY)); try { mCanvas.wait(); } catch (InterruptedException ie) { // Might as well check now .. } } } // Sleep for the inter-frame time, which is 1/25s = // 4ms. try { sleep(sleepTime); } catch (InterruptedException ie) { // Meh. } } } } // The actual play thread .. PlayThread mPlayThread; boolean mAdvancePending; boolean mKillAnimationThread; ImageCanvas(Composite inParent, MainFrame inContainingFrame, Shell inShell, int style) { super(inParent, style + SWT.NO_BACKGROUND); mContainingFrame = inContainingFrame; mMacroblockInfoCounter = 0; mShell = inShell; mWidth = mHeight = 0; mFrameNumber = 0; mFrameTotal = 0; mFormat = 0; mIsFieldMode = false; mZoomNumerator = 1; mZoomDenominator = 1; mMBMarkers = false; mAdvancePending = false; mKillAnimationThread = false; mBg = new Color(null, 255, 255, 255); mWhite = new Color(null, 255, 255, 255); // Create the play thread. setPlayMode(false); mPlayTo = -1; mPlayFrame = 0; mNextImagePending = false; mCurrentImage = mNextImage = null; mPlayThread = new PlayThread(this); mPlayThread.start(); setBackground(mBg); addDisposeListener(new Disposer()); addPaintListener(new Painter()); addMouseListener(new ClickLogger()); addListener(SWT.Resize, new Listener() { public void handleEvent(Event ev) { } }); addMouseMoveListener(new Mouser()); } // Instruct our animation thread to die. public void prepareForExit() { synchronized (this) { mKillAnimationThread = true; notifyAll(); } } /** Set the play mode * */ void setPlayMode(boolean inValue) { synchronized (this) { mPlayMode = inValue; mPlayFrame = 0; this.notifyAll(); } } /** Attempt to close a reader, emitting an error * dialog if we fail. * * @param reader The reader to close. */ void closeReader(RandomAccessFile reader) { // Do we have anything to do at all? if (reader == null) { return; } try { reader.close(); } catch (Exception ioe) { Utils.showMessageBox(mShell, "Cannot close " + mFilename + " - " + ioe.getMessage()); } } RandomAccessFile getReader() { if (mFilename == null) { return null; } File aFile = new File(mFilename); RandomAccessFile rv = null; if (!aFile.exists()) { Utils.showMessageBox(mShell, mFilename + " does not exist"); return null; } /* Open the file .. */ try { rv = new RandomAccessFile(aFile, "r"); } catch (Exception e) { Utils.showMessageBox (mShell, "Cannot open " + mFilename + " - " + e.getMessage()); return null; } return rv; } /** Opens a file. If we have X and Y coordinates, * we will attempt to display the file. * * @return true if we succeeded, false if we didn't * and displayed an error box. */ boolean openFile(String fileName) { File theFile = new File(fileName); if (!theFile.exists()) { Utils.showMessageBox(mShell, fileName + " does not exist."); return false; } RandomAccessFile reader = null; try { reader = new RandomAccessFile(theFile, "r"); } catch (Exception e) { Utils.showMessageBox (mShell, "Cannot open " + fileName + " - " + e.getMessage()); return false; } /* Otherwise .. */ if (reader != null) { closeReader(reader); } mFilename = fileName; updateImage(); return true; } /** Change the frame number we're looking at, and * update the image. */ void changeFrameNumber(int inFrameNumber) { if (inFrameNumber == mFrameNumber) { return; } mFrameNumber = inFrameNumber; mContainingFrame.transientInfo("Go to frame " + Integer.toString(mFrameNumber)); updateImage(); } /** Change the width, height and format settings, and * update the image. */ void changeFormat(int inWidth, int inHeight, int inFormat) { if (inWidth == mWidth && inHeight == mHeight && inFormat == mFormat) { // Nothing to do. return; } mWidth = inWidth; mHeight = inHeight; mFormat = inFormat; mContainingFrame.note("Changed format: " + Integer.toString(mWidth) + "x" + Integer.toString(mHeight) + " " + Utils.formatToString(mFormat)); updateImage(); } void makeNextImage() { if (mNextImage != null) { mNextImage.dispose(); } mNextImage =new BufferedImage(mFrameNumber + 1, this); mNextImage.convertImage(true); synchronized (this) { mNextImagePending = false; } } /** Try to read in the required image from the * given data. * * @return true if we managed to synthesise an * image, false if we didn't. */ boolean updateImage() { // Force a repaint. this.redraw(); synchronized (this) { if (mNextImage != null && mNextImage.mFrameNumber == mFrameNumber) { // We already have one :-). mCurrentImage.dispose(); mCurrentImage = mNextImage; mFrameNumber = mNextImage.mFrameNumber; mNextImage = null; if (!mCurrentImage.mReadyForDrawing) { this.redraw(); return false; } } else { if (mCurrentImage != null) { mCurrentImage.dispose(); } mCurrentImage = new BufferedImage(mFrameNumber, this); if (!mCurrentImage.convertImage(false)) { this.redraw(); return false; } } } // Resize ourselves, incidentally updating the status bar. this.updateSize(); // The redraw itself may not happen, so setting advancePending here // is the least worst we can do. synchronized (this) { mAdvancePending = false; } return true; } /** Update the size of this widget in response to an * image update or a zoom level update. */ void updateSize() { int actualWidth = (mWidth * mZoomNumerator) / mZoomDenominator; int actualHeight = (mHeight * mZoomNumerator) / mZoomDenominator; if (actualWidth == 0 && actualHeight == 0) { actualWidth = 720; actualHeight = 576; } Point currentSize = this.getSize(); if (actualWidth != currentSize.x || actualHeight != currentSize.y) { this.setSize(actualWidth, actualHeight); } this.setTitle(); mContainingFrame.updateStatus(); } public void dispose() { if (mCurrentImage != null) { mCurrentImage.dispose(); } if (mNextImage != null) { mNextImage.dispose(); } super.dispose(); } // Local function: set the window title to something // descriptive. void setTitle() { StringBuffer aTitle = new StringBuffer(); if (mFilename == null) { aTitle.append("YUV2 - No file loaded "); } else { aTitle.append("YUV2 - "); aTitle.append(mFilename); aTitle.append(" "); aTitle.append(Integer.toString(mWidth)); aTitle.append("x"); aTitle.append(Integer.toString(mHeight)); aTitle.append(" "); } aTitle.append(" F:"); aTitle.append(Integer.toString(mFrameNumber)); aTitle.append("/"); aTitle.append(Long.toString(mFrameTotal-1)); aTitle.append(" Z:"); aTitle.append(Integer.toString(mZoomNumerator)); aTitle.append("/"); aTitle.append(Integer.toString(mZoomDenominator)); mShell.setText(aTitle.toString()); } public void SetZoomNumerator(int inNumerator) { mZoomNumerator = inNumerator; mContainingFrame.transientInfo("Set zoom " + Integer.toString(mZoomNumerator) + ":" + Integer.toString(mZoomDenominator)); this.updateSize(); this.redraw(); } public void SetZoomDenominator(int inDenominator) { if (inDenominator < 1) { // Ha ha. Very funny :-) return; } mZoomDenominator = inDenominator; mContainingFrame.note("Set zoom " + Integer.toString(mZoomNumerator) + ":" + Integer.toString(mZoomDenominator)); this.updateSize(); this.redraw(); } /** Get Info about a particular pixel location in the widget. * */ StringBuffer getPixelInfo(int x, int y) { StringBuffer rv = new StringBuffer(); int pixel_x = (x * mZoomDenominator) / mZoomNumerator; int pixel_y = (y * mZoomDenominator) / mZoomNumerator; Point mb_frame = new Point(pixel_x/16, pixel_y/16); Point mb_field = new Point(pixel_x/16, (pixel_y >> 1)/16); rv.append("("); rv.append(Integer.toString(pixel_x)); rv.append(","); rv.append(Integer.toString(pixel_y)); rv.append(") MB "); if (mIsFieldMode) { rv.append("<" + Integer.toString(mb_field.x) + "," + Integer.toString(mb_field.y) + "," + Integer.toString(((pixel_y & 16) != 0)? 1 : 0) + ">"); rv.append(" @" + Integer.toString(pixel_x%16) + "," + Integer.toString((pixel_y>>1)%16)); } else { rv.append("( " + Integer.toString(mb_frame.x) + "," + Integer.toString(mb_frame.y) + ")"); rv.append(" @" + Integer.toString(pixel_x%16) + "," + Integer.toString(pixel_y%16)); } rv.append(" = "); if (pixel_x < mWidth && pixel_y <= mHeight && pixel_x >= 0 && pixel_y >= 0) { int yoffset = (pixel_y * mWidth) + pixel_x; int uvoffset = ((pixel_y>>1) * (mWidth >> 1)) + (pixel_x>>1); rv.append("0x" + Integer.toString(mCurrentImage.mYBuf[yoffset]&0xff, 16) + " "); rv.append("0x" + Integer.toString(mCurrentImage.mUBuf[uvoffset]&0xff, 16) + " "); rv.append("0x" + Integer.toString(mCurrentImage.mVBuf[uvoffset]&0xff, 16) + ">"); } return rv; } boolean hasValidImage() { return ((mCurrentImage != null) && (mCurrentImage.mImage != null) && (mCurrentImage.mUBuf != null) && (mCurrentImage.mVBuf != null) && (mCurrentImage.mYBuf != null)); } /** The user dragged on (x,y). Show them some information about * it. * * @param x * @param y */ void dropper(int x, int y, boolean clicked) { if (!hasValidImage()) { mContainingFrame.transientInfo ("No current image"); return; } StringBuffer rv = getPixelInfo(x,y); if (!clicked) { mContainingFrame.transientInfo(rv.toString()); } else { mContainingFrame.note(rv.toString()); } } /** The user double-clicked; dump macroblock info. * * * * */ void dumpMacroblock(int click_x, int click_y) { if (!hasValidImage()) { mContainingFrame.transientInfo ("No current image"); return; } StringBuilder logInfo = new StringBuilder(); int pixel_x = (click_x * mZoomDenominator) / mZoomNumerator; int pixel_y = (click_y * mZoomDenominator) / mZoomNumerator; logInfo.append(Integer.toString(mMacroblockInfoCounter) + " : Macroblock info at pixel " + Integer.toString(pixel_x) + "," + Integer.toString(pixel_y)); ++mMacroblockInfoCounter; if (pixel_x < 0 || pixel_y < 0 || pixel_x >= mWidth || pixel_y >= mHeight) { logInfo.append(": Coordinates out of range."); } else { // Actually do it. Point mb_coords = new Point(0,0); boolean topField; boolean inFieldMode = this.mIsFieldMode; // Formatter requires us to use a dummy array for its arguments. Object tmpO[] = new Object[1]; if (inFieldMode) { mb_coords.x = (pixel_x >> 4); mb_coords.y = (pixel_y&~1)>>4; topField = ((pixel_y&1) == 0) ? true : false; logInfo.append(" Field MB <" + Integer.toString(mb_coords.x) + "," + Integer.toString(mb_coords.y) + "," + (topField ? "0" : "1") + ">:\n"); // Now dump the MB { int y_offset = ((mb_coords.y * 32) + (topField ? 0 : 1)) * mWidth + mb_coords.x * 16; // the -16 is here to cope with macroblocks that run just // off the end of the array. int y_max = ((mWidth) * mHeight) - 16; int uv_offset = ((mb_coords.y * 16) + (topField ? 0 : 1)) * (mWidth >> 1) + (mb_coords.x * 8); int uv_max = ((mWidth>>1)*(mHeight>>1))-8; Formatter formatter = new Formatter(logInfo, Locale.UK); logInfo.append("Y:\n"); // y_offset += mWidth *2 because we're in field mode. for (int y = 0; y < 16 && y_offset <= y_max; ++y, y_offset += mWidth*2) { for (int x = 0; x < 16; ++x) { tmpO[0] = new Byte(mCurrentImage.mYBuf[y_offset + x]); formatter.format("%02x ", tmpO); } logInfo.append("\n"); } logInfo.append("\n"); logInfo.append("U:\n"); for (int y = 0, uv_cur = uv_offset; y < 8 && uv_cur <= uv_max; ++y, uv_cur += mWidth) { for (int x = 0; x < 8; ++x) { tmpO[0] = new Byte(mCurrentImage.mUBuf[uv_cur + x]); formatter.format("%02x ", tmpO); } logInfo.append("\n"); } logInfo.append("\n"); logInfo.append("V:\n"); for (int y = 0, uv_cur = uv_offset; y < 8 && uv_cur <= uv_max; ++y, uv_cur += mWidth) { for (int x = 0; x < 8; ++ x) { tmpO[0] = new Byte(mCurrentImage.mVBuf[uv_cur + x]); formatter.format("%02x ", tmpO); } logInfo.append("\n"); } } } else { mb_coords.x = (pixel_x >> 4); mb_coords.y = (pixel_y >> 4); topField = true; logInfo.append(" Frame MB (" + Integer.toString(mb_coords.x) + "," + Integer.toString(mb_coords.y) + "):\n"); /* Dump the macroblock */ { int y_offset = ((mb_coords.y * 16)*mWidth) + mb_coords.x*16; int y_max = (mWidth * mHeight) - 16; int uv_offset = ((mb_coords.y * 8) * (mWidth >> 1)) + mb_coords.x*8; int uv_max = ((mWidth >> 1) * (mHeight >> 1)) - 8; Formatter formatter = new Formatter(logInfo, Locale.UK); logInfo.append("Y:\n"); for (int y = 0; y < 16 && y_offset <= y_max; ++y, y_offset += mWidth) { for (int x = 0; x < 16 ; ++ x) { tmpO[0] = new Byte(mCurrentImage.mYBuf[y_offset + x]); formatter.format("%02x ", tmpO); } logInfo.append("\n"); } logInfo.append("\n"); logInfo.append("U:\n"); for (int y=0, uv_cur = uv_offset; y < 8 && uv_cur <= uv_max; ++y, uv_cur += mWidth >> 1) { for (int x = 0; x<8;++x) { tmpO[0] = new Byte(mCurrentImage.mUBuf[uv_cur + x]); formatter.format("%02x ", tmpO); } logInfo.append("\n"); } logInfo.append("\n"); logInfo.append("V:\n"); for (int y=0, uv_cur = uv_offset; y < 8 && uv_cur <= uv_max; ++y, uv_cur += mWidth >> 1) { for (int x = 0; x<8;++x) { tmpO[0] = new Byte(mCurrentImage.mVBuf[uv_cur + x]); formatter.format("%02x ", tmpO); } logInfo.append("\n"); } logInfo.append("\n"); } } } // Log everything .. mContainingFrame.log(logInfo.toString()); } void setFieldMode(boolean inIsField) { mIsFieldMode = inIsField; this.setTitle(); mContainingFrame.updateStatus(); this.redraw(); // << Markers may need updating. } void setMBMarkers(boolean inMBMarkers) { mMBMarkers = inMBMarkers; this.setTitle(); mContainingFrame.updateStatus(); redraw(); } class ClickLogger implements MouseListener { public void mouseDoubleClick(MouseEvent e) { // Got a double-click; dump this macroblock to the log. dumpMacroblock(e.x, e.y); } public void mouseDown(MouseEvent e) { // Double-click is almost always what you want, and the // single-click behaviour messes up the logs :-(. // dropper(e.x, e.y, true); } public void mouseUp(MouseEvent e) { // Do nothing. } } class Mouser implements MouseMoveListener { public void mouseMove(MouseEvent theEvent) { /* If you move the mouse over a pixel, and you have a * button held down, we activate the dropper. */ dropper(theEvent.x, theEvent.y, false); } } class Disposer implements DisposeListener { public void widgetDisposed(DisposeEvent e) { mBg.dispose(); } } class Painter implements PaintListener { public void paintControl(PaintEvent e) { int hDimension = (mWidth * mZoomNumerator)/ mZoomDenominator; int vDimension = (mHeight * mZoomNumerator) / mZoomDenominator; // Just to prove we exist :-). e.gc.setBackground(mBg); e.gc.setForeground(mWhite); synchronized (this) { if (mCurrentImage != null && mCurrentImage.mImage != null) { e.gc.drawImage(mCurrentImage.mImage, 0, 0, mWidth, mHeight, 0, 0, hDimension, vDimension); } else { e.gc.fillRectangle(e.x, e.y, e.width,e.height); } } /* Any macroblock markers? */ if (mMBMarkers) { /* Work out how big we're supposed to draw them .. */ int hStep = (16 * mZoomNumerator) / mZoomDenominator; int vStep = (16 * mZoomNumerator) / mZoomDenominator; /* If we're in field mode, double the Y stride * (actually, anything we do here will be wrong, but * Rhodri reckons this is the least wrong thing to do, * and I agree) */ if (mIsFieldMode) { vStep <<= 1; } /* If either of our steps were going to be zero, * don't bother drawing anything - we'll just obliterate * the image. */ if (hStep != 0 && vStep != 0) { e.gc.setXORMode(true); for (int j=0;j<=vDimension;j += vStep) { /* Draw the horizontals */ e.gc.drawLine(0, j, hDimension, j); } /* Draw the verticals. We get cancellation * at the edges, but this isn't too much of * a problem. */ for (int i=0;i<=hDimension;i += hStep) { e.gc.drawLine(i, 0, i, vDimension); } e.gc.setXORMode(false); } } } } } /* End file */ tstools-1.13~git20151030/yuv2/src/com/aminocom/yuv2/LogWindow.java000077500000000000000000000115551261471605300244370ustar00rootroot00000000000000/* * A log window. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * Kynesim, Cambridge UK * * ***** END LICENSE BLOCK ***** */ package com.aminocom.yuv2; import java.util.Vector; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; /*** This class wraps a shell that displays a log window, used * for persistent logging. The window is associated with a * MainFrame, and exists until closed. * * @author rrw * */ public class LogWindow { Shell mShell; ScrolledComposite mScrollArea; StyledText mData; boolean mDisposed; LogWindow(Display inDisplay, Vector logLines, int nr) { mShell = new Shell(inDisplay); mShell.setText("Log " + Integer.toString(nr)); mDisposed = false; { GridLayout rows = new GridLayout(); rows.numColumns = 1; mShell.setLayout(rows); } // mScrollArea = new ScrolledComposite(mShell, // SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); mData = new StyledText(mShell, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); mData.setEditable(false); // mScrollArea.setLayout(new FillLayout()); // Attempt to set a fixed pitch font. mData.setFont(new Font(null, new FontData("Courier", 8, SWT.NORMAL))); { GridData scrollData = new GridData(); scrollData.grabExcessHorizontalSpace = true; scrollData.grabExcessVerticalSpace = true; scrollData.verticalAlignment = SWT.FILL; scrollData.horizontalAlignment = SWT.FILL; mData.setLayoutData(scrollData); } // mScrollArea.setContent(mData); // mScrollArea.setExpandHorizontal(true); // mScrollArea.setExpandVertical(true); // mScrollArea.setAlwaysShowScrollBars(true); // mScrollArea.pack(); mData.pack(); { Composite buttonBar = new Composite(mShell, 0); buttonBar.setLayout(new RowLayout()); Button ok = new Button(buttonBar, 0); ok.setText("Dismiss"); ok.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { LogWindow.this.close(); } }); Button clear = new Button(buttonBar, 0); clear.setText("Clear"); clear.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { LogWindow.this.mData.setText(""); } }); { GridData barData = new GridData(GridData.HORIZONTAL_ALIGN_CENTER); barData.verticalAlignment = SWT.CENTER; barData.verticalAlignment = SWT.CENTER; barData.grabExcessHorizontalSpace = false; barData.grabExcessVerticalSpace = false; buttonBar.setLayoutData(barData); buttonBar.pack(); } } loadData(logLines); mShell.pack(); mShell.setSize(600, 200); mShell.open(); } /** Clear the log and load data into it. If you * set data to null, nothing will be loaded. */ void loadData(Vector inLines) { // Clear the content of the styled text. mData.setText(""); if (inLines != null) { for (int i=0;i"); mPlayButton.addListener( SWT.Selection, new Listener() { public void handleEvent(Event inEvent) { MainFrame.this.mImage.setPlayMode (((Button)inEvent.widget).getSelection()); } }); } toolBar.pack(); Point size = toolBar.getSize(); CoolItem rv = new CoolItem(inBar, SWT.NONE); rv.setControl(toolBar); Point preferredSize = rv.computeSize(size.x, size.y); rv.setPreferredSize(preferredSize); return rv; } /** Make the view menu */ private MenuItem makeViewMenu(Menu topMenu) { MenuItem viewMenu = new MenuItem(topMenu, SWT.CASCADE); Menu dropDown = new Menu(mShell, SWT.DROP_DOWN); viewMenu.setText("View"); viewMenu.setMenu(dropDown); { MenuItem item = new MenuItem(dropDown, SWT.PUSH); item.setText("&Log"); item.setAccelerator(SWT.CTRL + 'L'); item.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { openLog(); } }); } return viewMenu; }; /** Make the help menu */ private MenuItem makeHelpMenu(Menu topMenu) { MenuItem helpItem = new MenuItem(topMenu, SWT.CASCADE | SWT.RIGHT); helpItem.setText("Help"); Menu helpMenu = new Menu(mShell, SWT.DROP_DOWN); helpItem.setMenu(helpMenu); { MenuItem aboutItem = new MenuItem(helpMenu, SWT.PUSH); aboutItem.setText("About"); aboutItem.addListener(SWT.Selection, new Listener() { public void handleEvent(Event inEvent) { MainFrame.this.aboutBox(); }}); } return helpItem; } /** Make the recent items menu * */ private MenuItem makeRecentMenu(Menu topMenu) { MenuItem recentMenu = new MenuItem(topMenu, SWT.CASCADE); mRecentMenu = new Menu(mShell, SWT.DROP_DOWN); recentMenu.setText("Recent"); recentMenu.setMenu(mRecentMenu); mRecentMenu.addListener(SWT.Show, new Listener() { public void handleEvent(Event event) { fillRecentMenu(); } }); return recentMenu; }; /** Make the file menu. * * @param topMenu * @return */ private MenuItem makeFileMenu(Menu topMenu) { MenuItem fileMenu = new MenuItem(topMenu, SWT.CASCADE); Menu dropDown = new Menu(mShell, SWT.DROP_DOWN); fileMenu.setText("File"); fileMenu.setMenu(dropDown); // Open { MenuItem item = new MenuItem(dropDown, SWT.PUSH); item.setText("&Open"); item.setAccelerator(SWT.CTRL + 'O'); item.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { MainFrame.this.openFile(null); } }); } // Set parameters. { MenuItem item = new MenuItem(dropDown, SWT.PUSH); item.setText("&Dimensions"); item.setAccelerator(SWT.CTRL + 'D'); item.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { MainFrame.this.setFormat (mPrefs.getDimensions (mImage.mFilename)); } }); } // Separator { new MenuItem(dropDown, SWT.SEPARATOR); } // Clear log. { MenuItem item = new MenuItem(dropDown, SWT.PUSH); item.setText("&Clear logs"); item.setAccelerator(SWT.CTRL + 'C'); item.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { MainFrame.this.clearLog(); } }); } // Quit { MenuItem item = new MenuItem(dropDown, SWT.PUSH); item.setText("E&xit"); item.setAccelerator(SWT.CTRL + 'X'); item.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { MainFrame.this.quit(); } }); } return fileMenu; } /** Opens a log window, and adds it to the * vector of log windows we maintain. * * The idea here is that you can open many log windows.. * */ void openLog() { LogWindow newLog = new LogWindow(mShell.getDisplay(), mLogLines, mLogCounter); ++mLogCounter; mLogWindows.addElement(newLog); } private void clearLog() { boolean needTidy = false; // Clear the logs. mLogLines = new Vector(); for (int i=0;i= 0) { try { rv.x = Integer.parseInt(inString.substring(0, pos)); rv.y = Integer.parseInt(inString.substring(pos+1)); dimensionsOK = true; } catch (NumberFormatException nfe) { // Do nothing - we're about to issue an error // anyway. }; } return dimensionsOK; } /** Make form data from attachments * */ public static FormData makeFormData(FormAttachment inLeft, FormAttachment inRight, FormAttachment inTop, FormAttachment inBottom) { FormData rv = new FormData(); rv.left = inLeft; rv.right = inRight; rv.top = inTop; rv.bottom= inBottom; return rv; } public static void showMessageBox(Shell inShell, String s) { MessageBox mb = new MessageBox(inShell, SWT.OK); mb.setMessage(s); mb.open(); } } tstools-1.13~git20151030/yuv2/src/com/aminocom/yuv2/YUV2.java000077500000000000000000000107011261471605300232630ustar00rootroot00000000000000/* * YUV2 main program. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * Kynesim, Cambridge UK * * ***** END LICENSE BLOCK ***** */ package com.aminocom.yuv2; import org.eclipse.swt.widgets.Display; public class YUV2 { public static class WorkRequest { public static final int UPDATE_IMAGE = 0; public static final int STOP_PLAY = 1; public static final int ADVANCE_PLAY = 2; public static final int BUFFER_NEXT_IMAGE = 3; public static final int NOP = 2; int mCode; ImageCanvas mImageCanvas; MainFrame mMainFrame; WorkRequest next; WorkRequest() { mCode = NOP; mImageCanvas = null; mMainFrame = null; next = null; } WorkRequest(ImageCanvas inObject, int inCode) { mCode = inCode; mImageCanvas = inObject; mMainFrame = null; next = null; } WorkRequest(MainFrame inFrame, int inCode) { mCode = inCode; mImageCanvas = null; mMainFrame = inFrame; next = null; } }; /** A queue of things that need to be done by the UI * thread. */ static WorkRequest sDummyHead; /** This routine is safe for multithreading * We deliberately starve latecomers, first * because it's easy, second because we'd * rather give good service to one thread * and kill everyone else than do a bad * job everywhere. */ public static void addWork(WorkRequest newRequest) { Display responsibleDisplay = (newRequest.mImageCanvas != null ? newRequest.mImageCanvas.getDisplay() : newRequest.mMainFrame.mShell.getDisplay()); synchronized (sDummyHead) { newRequest.next = sDummyHead.next; sDummyHead.next = newRequest; } // Poke the display - new data just came in. responsibleDisplay.wake(); } /** Pop a work request off the queue. * * */ public static WorkRequest getWork() { WorkRequest popped = null; synchronized (sDummyHead) { if (sDummyHead.next != null) { popped = sDummyHead.next; sDummyHead.next = popped.next; popped.next = null; // Just in case. } } return popped; } /** Do a work request */ public static void DispatchWork(WorkRequest req) { switch (req.mCode) { case WorkRequest.UPDATE_IMAGE: req.mImageCanvas.updateImage(); break; case WorkRequest.ADVANCE_PLAY: req.mMainFrame.playAdvance(); break; case WorkRequest.STOP_PLAY: req.mMainFrame.playStopped(); break; case WorkRequest.BUFFER_NEXT_IMAGE: req.mImageCanvas.makeNextImage(); break; } } public static void main(String[] s) { String dimensions = null, filename = null; // Create the dummy work queue head (merely used // for synchronization). sDummyHead = new WorkRequest(); Display display = new Display(); for (int i=0;i