From 1496394c0f4be1a718fb189846c6350cbd9e43da Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 24 Oct 2002 22:37:51 +0000 Subject: First stab at porting mplex Original commit message from CVS: First stab at porting mplex --- ext/mplex/.gitignore | 6 + ext/mplex/COPYING | 339 +++++++++++++ ext/mplex/INSTRUCT | 67 +++ ext/mplex/Makefile.am | 38 ++ ext/mplex/README | 58 +++ ext/mplex/TODO | 41 ++ ext/mplex/ac3strm_in.cc | 382 +++++++++++++++ ext/mplex/audiostrm.hh | 163 +++++++ ext/mplex/audiostrm_out.cc | 144 ++++++ ext/mplex/aunit.hh | 41 ++ ext/mplex/bits.cc | 358 ++++++++++++++ ext/mplex/bits.hh | 82 ++++ ext/mplex/buffer.cc | 118 +++++ ext/mplex/buffer.hh | 73 +++ ext/mplex/fastintfns.h | 32 ++ ext/mplex/format_codes.h | 38 ++ ext/mplex/gstmplex.cc | 464 ++++++++++++++++++ ext/mplex/gstmplex.h | 97 ++++ ext/mplex/inputstrm.cc | 249 ++++++++++ ext/mplex/inputstrm.hh | 278 +++++++++++ ext/mplex/interact.cc | 196 ++++++++ ext/mplex/lpcmstrm_in.cc | 303 ++++++++++++ ext/mplex/mjpeg_logging.cc | 239 +++++++++ ext/mplex/mjpeg_logging.h | 79 +++ ext/mplex/mjpeg_types.h | 120 +++++ ext/mplex/mpastrm_in.cc | 329 +++++++++++++ ext/mplex/mpegconsts.cc | 427 +++++++++++++++++ ext/mplex/mpegconsts.h | 149 ++++++ ext/mplex/mplexconsts.hh | 83 ++++ ext/mplex/multplex.cc | 1120 +++++++++++++++++++++++++++++++++++++++++++ ext/mplex/outputstream.hh | 198 ++++++++ ext/mplex/padstrm.cc | 59 +++ ext/mplex/padstrm.hh | 73 +++ ext/mplex/stillsstream.cc | 193 ++++++++ ext/mplex/stillsstream.hh | 107 +++++ ext/mplex/systems.cc | 761 +++++++++++++++++++++++++++++ ext/mplex/systems.hh | 131 +++++ ext/mplex/vector.cc | 23 + ext/mplex/vector.hh | 71 +++ ext/mplex/videostrm.hh | 155 ++++++ ext/mplex/videostrm_in.cc | 429 +++++++++++++++++ ext/mplex/videostrm_out.cc | 276 +++++++++++ ext/mplex/yuv4mpeg.cc | 880 ++++++++++++++++++++++++++++++++++ ext/mplex/yuv4mpeg.h | 473 ++++++++++++++++++ ext/mplex/yuv4mpeg_intern.h | 85 ++++ ext/mplex/yuv4mpeg_ratio.cc | 167 +++++++ 46 files changed, 10194 insertions(+) create mode 100644 ext/mplex/.gitignore create mode 100644 ext/mplex/COPYING create mode 100644 ext/mplex/INSTRUCT create mode 100644 ext/mplex/Makefile.am create mode 100644 ext/mplex/README create mode 100644 ext/mplex/TODO create mode 100644 ext/mplex/ac3strm_in.cc create mode 100644 ext/mplex/audiostrm.hh create mode 100644 ext/mplex/audiostrm_out.cc create mode 100644 ext/mplex/aunit.hh create mode 100644 ext/mplex/bits.cc create mode 100644 ext/mplex/bits.hh create mode 100644 ext/mplex/buffer.cc create mode 100644 ext/mplex/buffer.hh create mode 100644 ext/mplex/fastintfns.h create mode 100644 ext/mplex/format_codes.h create mode 100644 ext/mplex/gstmplex.cc create mode 100644 ext/mplex/gstmplex.h create mode 100644 ext/mplex/inputstrm.cc create mode 100644 ext/mplex/inputstrm.hh create mode 100644 ext/mplex/interact.cc create mode 100644 ext/mplex/lpcmstrm_in.cc create mode 100644 ext/mplex/mjpeg_logging.cc create mode 100644 ext/mplex/mjpeg_logging.h create mode 100644 ext/mplex/mjpeg_types.h create mode 100644 ext/mplex/mpastrm_in.cc create mode 100644 ext/mplex/mpegconsts.cc create mode 100644 ext/mplex/mpegconsts.h create mode 100644 ext/mplex/mplexconsts.hh create mode 100644 ext/mplex/multplex.cc create mode 100644 ext/mplex/outputstream.hh create mode 100644 ext/mplex/padstrm.cc create mode 100644 ext/mplex/padstrm.hh create mode 100644 ext/mplex/stillsstream.cc create mode 100644 ext/mplex/stillsstream.hh create mode 100644 ext/mplex/systems.cc create mode 100644 ext/mplex/systems.hh create mode 100644 ext/mplex/vector.cc create mode 100644 ext/mplex/vector.hh create mode 100644 ext/mplex/videostrm.hh create mode 100644 ext/mplex/videostrm_in.cc create mode 100644 ext/mplex/videostrm_out.cc create mode 100644 ext/mplex/yuv4mpeg.cc create mode 100644 ext/mplex/yuv4mpeg.h create mode 100644 ext/mplex/yuv4mpeg_intern.h create mode 100644 ext/mplex/yuv4mpeg_ratio.cc diff --git a/ext/mplex/.gitignore b/ext/mplex/.gitignore new file mode 100644 index 000000000..684bfe5b1 --- /dev/null +++ b/ext/mplex/.gitignore @@ -0,0 +1,6 @@ +Makefile +Makefile.in +mplex +.deps +.libs + diff --git a/ext/mplex/COPYING b/ext/mplex/COPYING new file mode 100644 index 000000000..a43ea2126 --- /dev/null +++ b/ext/mplex/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ext/mplex/INSTRUCT b/ext/mplex/INSTRUCT new file mode 100644 index 000000000..e75d4cfa4 --- /dev/null +++ b/ext/mplex/INSTRUCT @@ -0,0 +1,67 @@ +////////////////////////////////////////////////////////////////////////// +// // +// INSTRUCTIONS FOR MPLEX - THE MPEG1/SYSTEMS MULTIPLEXER // +// // +////////////////////////////////////////////////////////////////////////// + + +Please note that I do not have a comprehensive instruction manual for this +release. I suggest you try the program out with some default values and +learn something more about ISO/IEC 11172-1 (aka MPEG1/Systems). + +For those of you that can read *German*, you can download a postscript +paper discussing implementation and problems of this software, with +introductions to MPEG1/Audio, MPEG1/Video and MPEG1/Systems. +You should find the paper with the same distribution you got this +program from. + +If not, you should find the postscript version of this 40-page paper +on + +ftp.informatik.tu-muenchen.de in /pub/comp/graphics/mpeg/mplex + +(121822 bytes, Jun 30 , 1994 , mpeg_systems_paper_0.99.ps.gz) + +If you have any questions you cannot figure out by running the +program, feel free to ask me. + +-------------------------------------------------------------------------- + +One more thing that might save me many emails: + +when asked about the startup packet delay, try something like +half the video buffer size divided by your sector size. Say you +have a 40 kByte video buffer and a 2324 Byte Sector size, then +a startup delay of 8 sectors will work just fine. + +What does the above parameter mean? + +Normally, the Decoding/Presentation Time Stamp of the first access +unit is set to the clock value that will happen exactly after the last +packet containig data from this first unit arrives into the system +target decoder. This works fine if the video/audio streams are of +*very perfectly constant* or the packet size are *very* small +(ideally: the size of one access unit, that would mean variable +packet length). +Anyway: this parameter allows you to say that the System Target +Decoder should start decoding the first access unit after he +gets (startup_packet_delay + size_of_first_access_units[av]) +packets of data. +This guarantees that the buffers are conveniently filled up. +Note that both the video stream offset and audio stream offset (ms) +add up even more bytes to this startup delay, but you can +tell conveniently that audio should start so many ms after video, +for example. + +Sorry for no further doc, enjoy multiplexing A/V :) + +Christoph. + +moar@heaven.zfe.siemens.de ++---------------------------------------+--------------------------------+ +| http://www.informatik.tu-muenchen.de/ | Christoph Moar | +| cgi-bin/nph-gateway/hphalle6/~moar/ | Kaulbachstr.29a | +| index.html | 80539 Munich | +| email:moar@informatik.tu-muenchen.de | voice: ++49 - 89 - 23862874 | ++---------------------------------------+--------------------------------+ + diff --git a/ext/mplex/Makefile.am b/ext/mplex/Makefile.am new file mode 100644 index 000000000..c2d672cb9 --- /dev/null +++ b/ext/mplex/Makefile.am @@ -0,0 +1,38 @@ +plugindir = $(libdir)/gst + +CC = g++ + +EXTRA_DIST = \ + README INSTRUCT TODO \ + .cvsignore + +MAINTAINERCLEANFILES = Makefile.in + +gstmplex_SOURCES = \ + bits.cc bits.hh \ + buffer.cc buffer.hh \ + videostrm.hh audiostrm.hh padstrm.hh \ + videostrm_in.cc padstrm.cc \ + lpcmstrm_in.cc mpastrm_in.cc ac3strm_in.cc \ + videostrm_out.cc audiostrm_out.cc \ + multplex.cc outputstream.hh \ + systems.cc systems.hh \ + vector.cc vector.hh mplexconsts.hh \ + inputstrm.cc inputstrm.hh aunit.hh \ + stillsstream.cc stillsstream.hh \ + mjpeg_logging.cc mjpeg_logging.h \ + yuv4mpeg.cc yuv4mpeg_ratio.cc yuv4mpeg.h \ + mpegconsts.cc mpegconsts.h + +plugin_LTLIBRARIES = libgstmplex.la + +libgstmplex_la_SOURCES = gstmplex.cc \ + $(gstmplex_SOURCES) + +libgstmplex_la_CXXFLAGS = $(GST_CFLAGS) +libgstmplex_la_LIBADD = -lstdc++ $(LIBM_LIBS) +libgstmplex_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = gstmplex.h + + diff --git a/ext/mplex/README b/ext/mplex/README new file mode 100644 index 000000000..c060c1f08 --- /dev/null +++ b/ext/mplex/README @@ -0,0 +1,58 @@ + +* mplex-2 - MPEG1/2 SYSTEMS/PROGRAM stream multiplexer +* +* Orginally based on mplex +* Copyright (C) 1994 1995 Christoph Moar +* Reengineered version in C++ +* Copyright (C) 2000,2001, 2002 Andrew Stevens +* +* as@comlab.ox.ac.uk + (Andrew Stevens) +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License in the file COPYING for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + + +MODIFICATIONS TO ORIGINAL + +July 2000 Andrew Stevens + Trivial modifications to support catenated audio stremas and + non-intractive use. +August 2000 Andrew Stevens + Modifed to support multiplexing of variable bit-rate + video streams. Seems to work well. +August 2000 Andrew Stevens + Removed use of temporary files for storing stream data. + Increased performance through the use of a more efficient bitstream + library. + Eliminated arithmetic overflow errors for large streams. + Initial support for multiplexing variable bit-rate streams. + +Novermber 2000 Andrew Stevens + Clean up code to suit modern compilers with 64-bit int support. + Cleaned up packet size calculations to make the code more flexible. + Initial MPEG-2 support + Support for splitting output streams (not yet completely implemented) + Support for multiplexing for VCD. + +Jan 2001- + + Reengineered in C++ + Support for SVCD. + Support for basic DVD + VBR Audio as well as video. + Clean stream splitting support. + Class structure to simplify adding new stream types + + Encapsulation and modularistion diff --git a/ext/mplex/TODO b/ext/mplex/TODO new file mode 100644 index 000000000..8b3930171 --- /dev/null +++ b/ext/mplex/TODO @@ -0,0 +1,41 @@ +TODO + +- Check if video and MPEG audio streams have the same eof bug as I found +in AC3 audio. + +- Need to add general facility for enforcing max STD buffer delay for audio + and for warning if constraints for particular formats are exceeded. + +- Make VBR more efficient (a skip for long periods where no sector is emitted). + + +- Complete tidying up the systems.cc structure. Non-duplication of the + header generation stuff would be neat if it can be managed... + + +- Add checking for changed sequence parameters in mid-sequence sequence headers. + + +- Currently the VCD HR Stills muxing stuff assumes *all* HR stills + are the same size which is given in the initial vbv_buffer_size... + This will work with mpeg2enc (which does this) but will fail fail fail + with other streams. + +- Rebuild initial delay / sequence splitting DTS adjustment stuff so + different streams can have different starting delays based on + *stream* parameters. I.e. delay should be delegated to the elementary + streams with only a sector_prefix offset set centrally. + +- Tidy code so Elementary streams handle their mux parameter initialisation + from cmd-line parameters *not* the output stream. + + + + +Eventually: + +- Full SVCD (MPEG audio extension) support. + +- DVD muxing and generation of info for .IFO's etc. + + diff --git a/ext/mplex/ac3strm_in.cc b/ext/mplex/ac3strm_in.cc new file mode 100644 index 000000000..5dfc22d18 --- /dev/null +++ b/ext/mplex/ac3strm_in.cc @@ -0,0 +1,382 @@ +/* + * ac3strm_in.c: AC3 Audio strem class members handling scanning and + * buffering raw input stream. + * + * Copyright (C) 2001 Andrew Stevens + * Copyright (C) 2000,2001 Brent Byeler for original header-structure + * parsing code. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include "audiostrm.hh" +#include "outputstream.hh" + + + +#define AC3_SYNCWORD 0x0b77 +#define AC3_PACKET_SAMPLES 1536 + +/// table for the available AC3 bitrates +static const unsigned int ac3_bitrate_index[32] = +{ + 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, + 224, 256, 320, 384, 448, 512, 576, 640, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const unsigned int ac3_frame_size[3][32] = +{ + {64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, + 448, 512, 640, 768, 896, 1024, 1152, 1280, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {69, 87, 104, 121, 139, 174, 208, 243, 278, 348, 417, + 487, 557, 696, 835, 975, 1114, 1253, 1393, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {96, 120, 144, 168, 192, 240, 288, 336, 384, 480, 576, + 672, 768, 960, 1152, 1344, 1536, 1728, 1920, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +}; + +/// table for the available AC3 frequencies +static const unsigned int ac3_frequency[4] = { 48000, 44100, 32000, 0 }; + + +AC3Stream::AC3Stream (IBitStream & ibs, OutputStream & into): +AudioStream (ibs, into) +{ +} + +bool +AC3Stream::Probe (IBitStream & bs) +{ + return bs.getbits (16) == AC3_SYNCWORD; +} + + +/************************************************************************* + * + * Reads initial stream parameters and displays feedback banner to users + * @param stream_num AC3 substream ID + *************************************************************************/ + + +void +AC3Stream::Init (const int stream_num) +{ + unsigned int framesize_code; + + this->stream_num = stream_num; + + MuxStream::Init (PRIVATE_STR_1, 1, // Buffer scale + default_buffer_size, + muxinto.vcd_zero_stuffing, + muxinto.buffers_in_audio, muxinto.always_buffers_in_audio); + mjpeg_info ("Scanning for header info: AC3 Audio stream %02x", stream_num); + + InitAUbuffer (); + AU_start = bs.bitcount (); + if (bs.getbits (16) == AC3_SYNCWORD) { + num_syncword++; + bs.getbits (16); // CRC field + frequency = bs.getbits (2); // Sample rate code + framesize_code = bs.getbits (6); // Frame size code + framesize = ac3_frame_size[frequency][framesize_code >> 1]; + framesize = (framesize_code & 1) && frequency == 1 ? (framesize + 1) << 1 : (framesize << 1); + + + size_frames[0] = framesize; + size_frames[1] = framesize; + num_frames[0]++; + access_unit.start = AU_start; + access_unit.length = framesize; + bit_rate = ac3_bitrate_index[framesize_code >> 1]; + samples_per_second = ac3_frequency[frequency]; + + + /* Presentation time-stamping */ + access_unit.PTS = static_cast < clockticks > (decoding_order) * + static_cast < clockticks > (AC3_PACKET_SAMPLES) * + static_cast < clockticks > (CLOCKS) / samples_per_second; + access_unit.DTS = access_unit.PTS; + access_unit.dorder = decoding_order; + ++decoding_order; + aunits.append (access_unit); + + } else { + mjpeg_error ("Invalid AC3 Audio stream header."); + exit (1); + } + + OutputHdrInfo (); +} + +/// @returns the current bitrate +unsigned int +AC3Stream::NominalBitRate () +{ + return bit_rate; +} + +/// Prefills the internal buffer for output multiplexing. +/// @param frames_to_buffer the number of audio frames to read ahead +void +AC3Stream::FillAUbuffer (unsigned int frames_to_buffer) +{ + unsigned int framesize_code; + + last_buffered_AU += frames_to_buffer; + mjpeg_debug ("Scanning %d MPEG audio frames to frame %d", frames_to_buffer, last_buffered_AU); + + static int header_skip = 5; // Initially skipped past 5 bytes of header + int skip; + bool bad_last_frame = false; + + while (!bs.eos () && + decoding_order < last_buffered_AU) { + skip = access_unit.length - header_skip; + if (skip & 0x1) + bs.getbits (8); + if (skip & 0x2) + bs.getbits (16); + skip = skip >> 2; + + for (int i = 0; i < skip; i++) { + bs.getbits (32); + } + + prev_offset = AU_start; + AU_start = bs.bitcount (); + if (AU_start - prev_offset != access_unit.length * 8) { + bad_last_frame = true; + break; + } + + /* Check we have reached the end of have another catenated + stream to process before finishing ... */ + if ((syncword = bs.getbits (16)) != AC3_SYNCWORD) { + if (!bs.eos ()) { + mjpeg_error_exit1 ("Can't find next AC3 frame - broken bit-stream?"); + } + break; + } + + bs.getbits (16); // CRC field + bs.getbits (2); // Sample rate code TOOD: check for change! + framesize_code = bs.getbits (6); + framesize = ac3_frame_size[frequency][framesize_code >> 1]; + framesize = (framesize_code & 1) && frequency == 1 ? (framesize + 1) << 1 : (framesize << 1); + + access_unit.start = AU_start; + access_unit.length = framesize; + access_unit.PTS = static_cast < clockticks > (decoding_order) * + static_cast < clockticks > (AC3_PACKET_SAMPLES) * + static_cast < clockticks > (CLOCKS) / samples_per_second;; + access_unit.DTS = access_unit.PTS; + access_unit.dorder = decoding_order; + decoding_order++; + aunits.append (access_unit); + num_frames[0]++; + + num_syncword++; + + +#ifdef DEBUG_AC3_HEADERS + /* Some stuff to generate frame-header information */ + printf ("bsid = %d\n", bs.getbits (5)); + printf ("bsmode = 0x%1x\n", bs.getbits (3)); + int acmode = bs.getbits (3); + + printf ("acmode = 0x%1x\n", acmode); + if ((acmode & 0x1) && (acmode != 1)) + printf ("cmixlev = %d\n", bs.getbits (2)); + if ((acmode & 0x4)) + printf ("smixlev = %d\n", bs.getbits (2)); + if (acmode == 2) + printf ("dsurr = %d\n", bs.getbits (2)); + printf ("lfeon = %d\n", bs.getbits (1)); + printf ("dialnorm = %02d\n", bs.getbits (5)); + int compre = bs.getbits (1); + + printf ("compre = %d\n", compre); + if (compre) + printf ("compr = %02d\n", bs.getbits (8)); + int langcode = bs.getbits (1); + + printf ("langcode = %d\n", langcode); + if (langcode) + printf ("langcod = 0x%02x\n", bs.getbits (8)); + + while (bs.bitcount () % 8 != 0) + bs.getbits (1); + header_skip = (bs.bitcount () - AU_start) / 8; +#endif + if (num_syncword >= old_frames + 10) { + mjpeg_debug ("Got %d frame headers.", num_syncword); + old_frames = num_syncword; + } + + + } + if (bad_last_frame) { + mjpeg_error_exit1 ("Last AC3 frame ended prematurely!\n"); + } + last_buffered_AU = decoding_order; + eoscan = bs.eos (); + +} + + +/// Closes the AC3 stream and prints some statistics. +void +AC3Stream::Close () +{ + stream_length = AU_start >> 3; + mjpeg_info ("AUDIO_STATISTICS: %02x", stream_id); + mjpeg_info ("Audio stream length %lld bytes.", stream_length); + mjpeg_info ("Syncwords : %8u", num_syncword); + mjpeg_info ("Frames : %8u padded", num_frames[0]); + mjpeg_info ("Frames : %8u unpadded", num_frames[1]); + + bs.close (); +} + +/************************************************************************* + OutputAudioInfo + gibt gesammelte Informationen zu den Audio Access Units aus. + + Prints information on audio access units +*************************************************************************/ + +void +AC3Stream::OutputHdrInfo () +{ + mjpeg_info ("AC3 AUDIO STREAM:"); + + mjpeg_info ("Bit rate : %8u bytes/sec (%3u kbit/sec)", bit_rate * 128, bit_rate); + + if (frequency == 3) + mjpeg_info ("Frequency : reserved"); + else + mjpeg_info ("Frequency : %d Hz", ac3_frequency[frequency]); + +} + +/** +Reads the bytes neccessary to complete the current packet payload. +@param to_read number of bytes to read +@param dst byte buffer pointer to read to +@returns the number of bytes read + */ +unsigned int +AC3Stream::ReadPacketPayload (uint8_t * dst, unsigned int to_read) +{ + static unsigned int aus = 0; + static unsigned int rd = 0; + + unsigned int bytes_read = bs.read_buffered_bytes (dst + 4, to_read - 4); + + rd += bytes_read; + clockticks decode_time; + + unsigned int first_header = (new_au_next_sec || au_unsent > bytes_read) + ? 0 : au_unsent; + + // BUG BUG BUG: how do we set the 1st header pointer if we have + // the *middle* part of a large frame? + assert (first_header <= to_read - 2); + + + unsigned int syncwords = 0; + unsigned int bytes_muxed = bytes_read; + + if (bytes_muxed == 0 || MuxCompleted ()) { + goto completion; + } + + + /* Work through what's left of the current AU and the following AU's + updating the info until we reach a point where an AU had to be + split between packets. + NOTE: It *is* possible for this loop to iterate. + + The DTS/PTS field for the packet in this case would have been + given the that for the first AU to start in the packet. + + */ + + decode_time = RequiredDTS (); + while (au_unsent < bytes_muxed) { + // BUG BUG BUG: if we ever had odd payload / packet size we might + // split an AC3 frame in the middle of the syncword! + assert (bytes_muxed > 1); + bufmodel.Queued (au_unsent, decode_time); + bytes_muxed -= au_unsent; + if (new_au_next_sec) + ++syncwords; + aus += au->length; + if (!NextAU ()) { + goto completion; + } + new_au_next_sec = true; + decode_time = RequiredDTS (); + }; + + // We've now reached a point where the current AU overran or + // fitted exactly. We need to distinguish the latter case + // so we can record whether the next packet starts with an + // existing AU or not - info we need to decide what PTS/DTS + // info to write at the start of the next packet. + + if (au_unsent > bytes_muxed) { + if (new_au_next_sec) + ++syncwords; + bufmodel.Queued (bytes_muxed, decode_time); + au_unsent -= bytes_muxed; + new_au_next_sec = false; + } else // if (au_unsent == bytes_muxed) + { + bufmodel.Queued (bytes_muxed, decode_time); + if (new_au_next_sec) + ++syncwords; + aus += au->length; + new_au_next_sec = NextAU (); + } +completion: + // Generate the AC3 header... + // Note the index counts from the low byte of the offset so + // the smallest value is 1! + + dst[0] = AC3_SUB_STR_0 + stream_num; + dst[1] = syncwords; + dst[2] = (first_header + 1) >> 8; + dst[3] = (first_header + 1) & 0xff; + + return bytes_read + 4; +} + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/audiostrm.hh b/ext/mplex/audiostrm.hh new file mode 100644 index 000000000..bc0167acb --- /dev/null +++ b/ext/mplex/audiostrm.hh @@ -0,0 +1,163 @@ + +/* + * inptstrm.hh: Input stream classes for MPEG multiplexing + * TODO: Split into the base classes and the different types of + * actual input stream. + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __AUDIOSTRM_H__ +#define __AUDIOSTRM_H__ + +#include "inputstrm.hh" + +class AudioStream:public ElementaryStream +{ +public: + AudioStream (IBitStream & ibs, OutputStream & into); + virtual void Init (const int stream_num) = 0; + virtual void Close () = 0; + + void OutputSector (); + bool RunOutComplete (); + virtual unsigned int NominalBitRate () = 0; + + unsigned int num_syncword; + unsigned int num_frames[2]; + unsigned int size_frames[2]; + unsigned int version_id; + unsigned int layer; + unsigned int protection; + unsigned int bit_rate_code; + unsigned int frequency; + unsigned int mode; + unsigned int mode_extension; + unsigned int copyright; + unsigned int original_copy; + unsigned int emphasis; + +protected: + virtual bool AUBufferNeedsRefill (); + virtual void FillAUbuffer (unsigned int frames_to_buffer) = 0; + void InitAUbuffer (); + + /* State variables for scanning source bit-stream */ + unsigned int framesize; + unsigned int skip; + unsigned int samples_per_second; + unsigned long long int length_sum; + AAunit access_unit; +}; + +class MPAStream:public AudioStream +{ +public: + MPAStream (IBitStream & ibs, OutputStream & into); + virtual void Init (const int stream_num); + static bool Probe (IBitStream & bs); + virtual void Close (); + virtual unsigned int NominalBitRate (); + + +private: + void OutputHdrInfo (); + unsigned int SizeFrame (int bit_rate, int padding_bit); + virtual void FillAUbuffer (unsigned int frames_to_buffer); + + + /* State variables for scanning source bit-stream */ + unsigned int framesize; + unsigned int skip; + unsigned int samples_per_second; +}; + + + +class AC3Stream:public AudioStream +{ +public: + AC3Stream (IBitStream & ibs, OutputStream & into); + virtual void Init (const int stream_num); + static bool Probe (IBitStream & bs); + virtual void Close (); + virtual unsigned int NominalBitRate (); + + virtual unsigned int ReadPacketPayload (uint8_t * dst, unsigned int to_read); + virtual unsigned int StreamHeaderSize () + { + return 4; + } + + +private: + void OutputHdrInfo (); + virtual void FillAUbuffer (unsigned int frames_to_buffer); + + static const unsigned int default_buffer_size = 16 * 1024; + + /* State variables for scanning source bit-stream */ + unsigned int framesize; + unsigned int samples_per_second; + unsigned int bit_rate; + unsigned int stream_num; +}; + +class LPCMStream:public AudioStream +{ +public: + LPCMStream (IBitStream & ibs, OutputStream & into); + virtual void Init (const int stream_num); + static bool Probe (IBitStream & bs); + virtual void Close (); + virtual unsigned int NominalBitRate (); + + virtual unsigned int ReadPacketPayload (uint8_t * dst, unsigned int to_read); + virtual unsigned int StreamHeaderSize () + { + return 7; + } + + +private: + void OutputHdrInfo (); + virtual void FillAUbuffer (unsigned int frames_to_buffer); + + static const unsigned int default_buffer_size = 232 * 1024; + static const unsigned int ticks_per_frame_90kHz = 150; + + /* State variables for scanning source bit-stream */ + unsigned int stream_num; + unsigned int samples_per_second; + unsigned int channels; + unsigned int bits_per_sample; + unsigned int bytes_per_frame; + unsigned int frame_index; + unsigned int dynamic_range_code; +}; + + +#endif // __AUDIOSTRM_H__ + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/audiostrm_out.cc b/ext/mplex/audiostrm_out.cc new file mode 100644 index 000000000..5ef358af0 --- /dev/null +++ b/ext/mplex/audiostrm_out.cc @@ -0,0 +1,144 @@ + +/* + * inptstrm.c: Members of audi stream classes related to muxing out into + * the output stream. + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include +#include +#include "fastintfns.h" +#include "audiostrm.hh" +#include "outputstream.hh" + + + +AudioStream::AudioStream (IBitStream & ibs, OutputStream & into): +ElementaryStream (ibs, into, ElementaryStream::audio), num_syncword (0) +{ + FRAME_CHUNK = 24; + for (int i = 0; i < 2; ++i) + num_frames[i] = size_frames[i] = 0; +} + +void +AudioStream::InitAUbuffer () +{ + unsigned int i; + + for (i = 0; i < aunits.BUF_SIZE; ++i) + aunits.init (new AAunit); +} + + + +/********************************* + * Signals when audio stream has completed mux run-out specified + * in associated mux stream. + *********************************/ + +bool +AudioStream::RunOutComplete () +{ + return (au_unsent == 0 || (muxinto.running_out && RequiredPTS () >= muxinto.runout_PTS)); +} + +bool +AudioStream::AUBufferNeedsRefill () +{ + return + !eoscan + && (aunits.current () + FRAME_CHUNK > last_buffered_AU + || bs.buffered_bytes () < muxinto.sector_size); +} + +/****************************************************************** + Output_Audio + generates Pack/Sys Header/Packet information from the + audio stream and saves them into the sector +******************************************************************/ + +void +AudioStream::OutputSector () +{ + clockticks PTS; + unsigned int max_packet_data; + unsigned int actual_payload; + unsigned int old_au_then_new_payload; + + PTS = RequiredDTS (); + old_au_then_new_payload = muxinto.PacketPayload (*this, buffers_in_header, false, false); + + max_packet_data = 0; + if (muxinto.running_out && NextRequiredPTS () > muxinto.runout_PTS) { + /* We're now in the last AU of a segment. So we don't want to + go beyond it's end when writing sectors. Hence we limit + packet payload size to (remaining) AU length. + */ + max_packet_data = au_unsent; + } + + /* CASE: packet starts with new access unit */ + + if (new_au_next_sec) { + actual_payload = + muxinto.WritePacket (max_packet_data, *this, buffers_in_header, PTS, 0, TIMESTAMPBITS_PTS); + + } + + + /* CASE: packet starts with old access unit, no new one */ + /* starts in this very same packet */ + else if (!(new_au_next_sec) && (au_unsent >= old_au_then_new_payload)) { + actual_payload = + muxinto.WritePacket (max_packet_data, *this, buffers_in_header, 0, 0, TIMESTAMPBITS_NO); + } + + + /* CASE: packet starts with old access unit, a new one */ + /* starts in this very same packet */ + else { /* !(new_au_next_sec) && (au_unsent < old_au_then_new_payload)) */ + + /* is there another access unit anyway ? */ + if (Lookahead () != 0) { + PTS = NextRequiredDTS (); + actual_payload = + muxinto.WritePacket (max_packet_data, *this, buffers_in_header, PTS, 0, TIMESTAMPBITS_PTS); + + } else { + actual_payload = muxinto.WritePacket (0, *this, buffers_in_header, 0, 0, TIMESTAMPBITS_NO); + }; + + } + + ++nsec; + + buffers_in_header = always_buffers_in_header; + +} + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/aunit.hh b/ext/mplex/aunit.hh new file mode 100644 index 000000000..0876887bf --- /dev/null +++ b/ext/mplex/aunit.hh @@ -0,0 +1,41 @@ +#ifndef __AUNIT_H__ +#define __AUNIT_H__ + +#include +#include "mjpeg_types.h" +#include "bits.hh" + +typedef int64_t clockticks; // This value *must* be signed + + // because we frequently compute *offsets* + +class Aunit +{ +public: + Aunit ():length (0), PTS (0), DTS (0) + { + } + void markempty () + { + length = 0; + } + bitcount_t start; + unsigned int length; + clockticks PTS; + int dorder; + + // Used only for video AU's but otherwise + // you have to go crazy on templates. + clockticks DTS; + int porder; + unsigned int type; + bool seq_header; + bool end_seq; + +}; + +typedef Aunit VAunit; + +typedef Aunit AAunit; + +#endif // __AUNIT_H__ diff --git a/ext/mplex/bits.cc b/ext/mplex/bits.cc new file mode 100644 index 000000000..84d05919a --- /dev/null +++ b/ext/mplex/bits.cc @@ -0,0 +1,358 @@ +/** @file bits.cc, bit-level output */ + +/* Copyright (C) 2001, Andrew Stevens * + + * + * Disclaimer of Warranty + * + * These software programs are available to the user without any license fee or + * royalty on an "as is" basis. The MPEG Software Simulation Group disclaims + * any and all warranties, whether express, implied, or statuary, including any + * implied warranties or merchantability or of fitness for a particular + * purpose. In no event shall the copyright-holder be liable for any + * incidental, punitive, or consequential damages of any kind whatsoever + * arising from the use of these programs. + * + * This disclaimer of warranty extends to the user of these programs and user's + * customers, employees, agents, transferees, successors, and assigns. + * + * The MPEG Software Simulation Group does not represent or warrant that the + * programs furnished hereunder are free of infringement of any third-party + * patents. + * + * Commercial implementations of MPEG-1 and MPEG-2 video, including shareware, + * are subject to royalty fees to patent holders. Many of these patents are + * general enough such that they are unavoidable regardless of implementation + * design. + * + */ + +#include +#include +#include +#include +#include +#include +#include "mjpeg_logging.h" +#include "bits.hh" + + +/// Initializes the bitstream, sets internal variables. +// TODO: The buffer size should be set dynamically to sensible sizes. +// +BitStream::BitStream (): +user_data (NULL) +{ + totbits = 0LL; + buffer_start = 0LL; + eobs = true; + readpos = 0LL; + bfr = 0; + bfr_size = 0; +} + +/// Deconstructor. Deletes the internal buffer. +BitStream::~BitStream () +{ + delete bfr; +} + +/** + Refills an IBitStream's input buffer based on the internal variables bufcount and bfr_size. + */ +bool +IBitStream::refill_buffer () +{ + size_t i; + + if (bufcount >= bfr_size) { + SetBufSize (bfr_size + 4096); + } + + i = read_callback (this, bfr + bufcount, static_cast < size_t > (bfr_size - bufcount), user_data); + bufcount += i; + + if (i == 0) { + eobs = true; + return false; + } + return true; +} + +/** + Flushes all read input up-to *but not including* bit + unbuffer_upto. +@param flush_to the number of bits to flush +*/ + +void +IBitStream::flush (bitcount_t flush_upto) +{ + if (flush_upto > buffer_start + bufcount) + mjpeg_error_exit1 ("INTERNAL ERROR: attempt to flush input beyond buffered amount"); + + if (flush_upto < buffer_start) + mjpeg_error_exit1 + ("INTERNAL ERROR: attempt to flush input stream before first buffered byte %d last is %d", + (int) flush_upto, (int) buffer_start); + unsigned int bytes_to_flush = static_cast < unsigned int >(flush_upto - buffer_start); + + // + // Don't bother actually flushing until a good fraction of a buffer + // will be cleared. + // + + if (bytes_to_flush < bfr_size * 3 / 4) + return; + + bufcount -= bytes_to_flush; + buffer_start = flush_upto; + byteidx -= bytes_to_flush; + memmove (bfr, bfr + bytes_to_flush, static_cast < size_t > (bufcount)); +} + + +/** + Undo scanning / reading + N.b buffer *must not* be flushed between prepareundo and undochanges. + @param undo handle to store the undo information +*/ +void +IBitStream::prepareundo (BitStreamUndo & undo) +{ + undo = *(static_cast < BitStreamUndo * >(this)); +} + +/** +Undoes changes committed to an IBitStream. +@param undo handle to retrieve the undo information + */ +void +IBitStream::undochanges (BitStreamUndo & undo) +{ + *(static_cast < BitStreamUndo * >(this)) = undo; +} + +/** + Read a number bytes over an IBitStream, using the buffer. + @param dst buffer to read to + @param length the number of bytes to read + */ +unsigned int +IBitStream::read_buffered_bytes (uint8_t * dst, unsigned int length) +{ + unsigned int to_read = length; + + if (readpos < buffer_start) + mjpeg_error_exit1 + ("INTERNAL ERROR: access to input stream buffer @ %d: before first buffered byte (%d)", + (int) readpos, (int) buffer_start); + + if (readpos + length > buffer_start + bufcount) { + /* + if (!feof (fileh)) { + mjpeg_error + ("INTERNAL ERROR: access to input stream buffer beyond last buffered byte @POS=%lld END=%d REQ=%lld + %d bytes", + readpos, bufcount, readpos - (bitcount_t) buffer_start, length); + abort (); + } + */ + to_read = static_cast < unsigned int >((buffer_start + bufcount) - readpos); + } + memcpy (dst, bfr + (static_cast < unsigned int >(readpos - buffer_start)), to_read); + // We only ever flush up to the start of a read as we + // have only scanned up to a header *beginning* a block that is then + // read + flush (readpos); + readpos += to_read; + return to_read; +} + +/** open the device to read the bit stream from it +@param bs_filename filename to open +@param buf_size size of the internal buffer +*/ +void +IBitStream::open (ReadCallback read_callback, void *user_data, unsigned int buf_size) +{ + this->read_callback = read_callback; + this->user_data = user_data; + + bfr_size = buf_size; + if (bfr == NULL) + bfr = new uint8_t[buf_size]; + else { + delete bfr; + + bfr = new uint8_t[buf_size]; + } + + byteidx = 0; + bitidx = 8; + totbits = 0LL; + bufcount = 0; + eobs = false; + if (!refill_buffer ()) { + if (bufcount == 0) { + mjpeg_error_exit1 ("Unable to read."); + } + } +} + + +/** sets the internal buffer size. + @param new_buf_size the new internal buffer size +*/ +void +IBitStream::SetBufSize (unsigned int new_buf_size) +{ + assert (bfr != NULL); // Must be open first! + assert (new_buf_size >= bfr_size); // Can only be increased in size... + + if (bfr_size != new_buf_size) { + uint8_t *new_buf = new uint8_t[new_buf_size]; + + memcpy (new_buf, bfr, static_cast < size_t > (bfr_size)); + delete bfr; + + bfr_size = new_buf_size; + bfr = new_buf; + } + +} + +/** + close the device containing the bit stream after a read process +*/ +void +IBitStream::close () +{ +} + + +// TODO replace with shift ops! + +uint8_t + IBitStream::masks[8] = { + 0x1, + 0x2, + 0x4, + 0x8, + 0x10, + 0x20, + 0x40, +0x80 }; + +/*read 1 bit from the bit stream +@returns the read bit, 0 on EOF */ +uint32_t +IBitStream::get1bit () +{ + unsigned int bit; + + if (eobs) + return 0; + + bit = (bfr[byteidx] & masks[bitidx - 1]) >> (bitidx - 1); + totbits++; + bitidx--; + if (!bitidx) { + bitidx = 8; + byteidx++; + if (byteidx == bufcount) { + refill_buffer (); + } + } + + return bit; +} + +/*read N bits from the bit stream +@returns the read bits, 0 on EOF */ +uint32_t +IBitStream::getbits (int N) +{ + uint32_t val = 0; + int i = N; + unsigned int j; + + // Optimize: we are on byte boundary and want to read multiple of bytes! + if ((bitidx == 8) && ((N & 7) == 0)) { + i = N >> 3; + while (i > 0) { + if (eobs) + return 0; + val = (val << 8) | bfr[byteidx]; + byteidx++; + totbits += 8; + if (byteidx == bufcount) { + refill_buffer (); + } + i--; + } + } else { + while (i > 0) { + if (eobs) + return 0; + + j = (bfr[byteidx] & masks[bitidx - 1]) >> (bitidx - 1); + totbits++; + bitidx--; + if (!bitidx) { + bitidx = 8; + byteidx++; + if (byteidx == bufcount) { + refill_buffer (); + } + } + val = (val << 1) | j; + i--; + } + } + return val; +} + + +/** This function seeks for a byte aligned sync word (max 32 bits) in the bit stream and + places the bit stream pointer right after the sync. + This function returns 1 if the sync was found otherwise it returns 0 +@param sync the sync word to search for +@param N the number of bits to retrieve +@param lim number of bytes to search through +@returns false on error */ + +bool +IBitStream::seek_sync (uint32_t sync, int N, int lim) +{ + uint32_t val, val1; + uint32_t maxi = ((1U << N) - 1); /* pow(2.0, (double)N) - 1 */ ; + if (maxi == 0) { + maxi = 0xffffffff; + } + while (bitidx != 8) { + get1bit (); + } + + val = getbits (N); + if (eobs) + return false; + while ((val & maxi) != sync && --lim) { + val <<= 8; + val1 = getbits (8); + val |= val1; + if (eobs) + return false; + } + + return (!!lim); +} + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/bits.hh b/ext/mplex/bits.hh new file mode 100644 index 000000000..275b8c0f8 --- /dev/null +++ b/ext/mplex/bits.hh @@ -0,0 +1,82 @@ +#ifndef __BITS_H__ +#define __BITS_H__ + +#include +#include + +typedef uint64_t bitcount_t; + + +class BitStreamUndo +{ +//protected: +public: + uint8_t outbyte; + unsigned int byteidx; + int bitidx; + unsigned int bufcount; + fpos_t actpos; + bitcount_t totbits; + bitcount_t buffer_start; + bitcount_t readpos; + uint8_t *bfr; + unsigned int bfr_size; +public: + bool eobs; +}; + +typedef class BitStream _BitStream; + +typedef size_t (*ReadCallback) (_BitStream *bs, uint8_t *dest, size_t size, void *user_data); + +class BitStream : public BitStreamUndo +{ +public: + void *user_data; + static const unsigned int BUFFER_SIZE = 4 * 1024; +public: + BitStream (); + ~BitStream (); + inline bitcount_t bitcount () + { + return totbits; + } + inline bool eos () + { + return eobs; + } +}; + + +// +// Input bit stream class. Supports the "scanning" of a stream +// into a large buffer which is flushed once it has been read. +// N.b. if you scan ahead a long way and don't read its your +// responsibility to flush manually... +// + +class IBitStream:public BitStream +{ +public: + void open (ReadCallback read_callback, void *user_data, unsigned int buf_size = BUFFER_SIZE); + void SetBufSize (unsigned int buf_size); + void close (); + uint32_t get1bit (); + uint32_t getbits (int N); + void prepareundo (BitStreamUndo & undobuf); + void undochanges (BitStreamUndo & undobuf); + bool seek_sync (unsigned int sync, int N, int lim); + void flush (bitcount_t bitposition); + inline unsigned int buffered_bytes () + { + return (buffer_start + bufcount - readpos); + } + unsigned int read_buffered_bytes (uint8_t * dst, unsigned int length_bytes); + +private: + bool refill_buffer (); + static uint8_t masks[8]; + ReadCallback read_callback; +}; + +#endif // __BITS_H__ diff --git a/ext/mplex/buffer.cc b/ext/mplex/buffer.cc new file mode 100644 index 000000000..2b2918025 --- /dev/null +++ b/ext/mplex/buffer.cc @@ -0,0 +1,118 @@ +#include "buffer.hh" +#include + +/****************************************************************** + * Remove entries from FIFO buffer list, if their DTS is less than + * actual SCR. These packet data have been already decoded and have + * been removed from the system target decoder's elementary stream + * buffer. + *****************************************************************/ + +void +BufferModel::Cleaned (clockticks SCR) +{ + BufferQueue *pointer; + + while ((first != NULL) && first->DTS < SCR) { + pointer = first; + first = first->next; + delete pointer; + } +} + +/****************************************************************** + * Return the SCR when there will next be some change in the + * buffer. + * If the buffer is empty return a zero timestamp. + *****************************************************************/ + +clockticks +BufferModel::NextChange () +{ + if (first == NULL) + return static_cast < clockticks > (0); + else + return first->DTS; +} + + +/****************************************************************** + * + * Remove all entries from FIFO buffer list, if their DTS is less + * than actual SCR. These packet data have been already decoded and + * have been removed from the system target decoder's elementary + * stream buffer. + *****************************************************************/ + +void +BufferModel::Flushed () +{ + BufferQueue *pointer; + + while (first != NULL) { + pointer = first; + first = first->next; + delete pointer; + } +} + +/****************************************************************** + BufferModel::Space + + returns free space in the buffer +******************************************************************/ + +unsigned int +BufferModel::Space () +{ + unsigned int used_bytes; + BufferQueue *pointer; + + pointer = first; + used_bytes = 0; + + while (pointer != NULL) { + used_bytes += pointer->size; + pointer = pointer->next; + } + + return (max_size - used_bytes); + +} + +/****************************************************************** + Queue_Buffer + + adds entry into the buffer FIFO queue +******************************************************************/ + +void +BufferModel::Queued (unsigned int bytes, clockticks TS) +{ + BufferQueue *pointer; + + pointer = first; + if (pointer == NULL) { + first = new BufferQueue; + first->size = bytes; + first->next = NULL; + first->DTS = TS; + } else { + while ((pointer->next) != NULL) { + pointer = pointer->next; + } + + pointer->next = (BufferQueue *) malloc (sizeof (BufferQueue)); + pointer->next->size = bytes; + pointer->next->next = NULL; + pointer->next->DTS = TS; + } +} + + +void +BufferModel::Init (unsigned int size) +{ + max_size = size; + first = 0; +} diff --git a/ext/mplex/buffer.hh b/ext/mplex/buffer.hh new file mode 100644 index 000000000..a7bdba9e6 --- /dev/null +++ b/ext/mplex/buffer.hh @@ -0,0 +1,73 @@ + +/* + * buffer.hh: Classes for decoder buffer models for mux despatch + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef __BUFFER_H__ +#define __BUFFER_H__ + +#include +#include "aunit.hh" + +class BufferQueue +{ +#include +# +public: + unsigned int size; /* als verkettete Liste implementiert */ + clockticks DTS; + BufferQueue *next; +}; + + +class BufferModel +{ +public: + BufferModel ():max_size (0), first (0) + { + } + void Init (unsigned int size); + + void Cleaned (clockticks timenow); + clockticks NextChange (); + void Flushed (); + unsigned int Space (); + void Queued (unsigned int bytes, clockticks removaltime); + inline unsigned int Size () + { + return max_size; + } +private: + unsigned int max_size; + BufferQueue *first; +}; + + + +#endif // __BUFFER_H__ + + +/* + * Local variables: + * c-file-style: "gnu" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/fastintfns.h b/ext/mplex/fastintfns.h new file mode 100644 index 000000000..db78af1e8 --- /dev/null +++ b/ext/mplex/fastintfns.h @@ -0,0 +1,32 @@ +/* fast int primitives. min,max,abs,samesign + * + * WARNING: Assumes 2's complement arithmetic. + * + */ + + +static __inline__ int intmax( register int x, register int y ) +{ + return x < y ? y : x; +} + +static __inline__ int intmin( register int x, register int y ) +{ + return x < y ? x : y; +} + +static __inline__ int intabs( register int x ) +{ + return x < 0 ? -x : x; +} + +#define fabsshift ((8*sizeof(unsigned int))-1) + +#define signmask(x) (((int)x)>>fabsshift) +static __inline__ int intsamesign(int x, int y) +{ + return (y+(signmask(x) & -(y<<1))); +} +#undef signmask +#undef fabsshift + diff --git a/ext/mplex/format_codes.h b/ext/mplex/format_codes.h new file mode 100644 index 000000000..32f668f66 --- /dev/null +++ b/ext/mplex/format_codes.h @@ -0,0 +1,38 @@ +/* + $Id$ + + Copyright (C) 2001 Andrew Stevens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __FORMAT_CODES_H__ +#define __FORMAT_CODES_H__ + +#define MPEG_FORMAT_MPEG1 0 +#define MPEG_FORMAT_VCD 1 +#define MPEG_FORMAT_VCD_NSR 2 +#define MPEG_FORMAT_MPEG2 3 +#define MPEG_FORMAT_SVCD 4 +#define MPEG_FORMAT_SVCD_NSR 5 +#define MPEG_FORMAT_VCD_STILL 6 +#define MPEG_FORMAT_SVCD_STILL 7 +#define MPEG_FORMAT_DVD 8 + +#define MPEG_FORMAT_FIRST 0 +#define MPEG_FORMAT_LAST 8 + +#define MPEG_STILLS_FORMAT(x) (x==MPEG_FORMAT_VCD_STILL||x==MPEG_FORMAT_SVCD_STILL) +#endif /* __FORMAT_CODES_H__ */ diff --git a/ext/mplex/gstmplex.cc b/ext/mplex/gstmplex.cc new file mode 100644 index 000000000..73d78f069 --- /dev/null +++ b/ext/mplex/gstmplex.cc @@ -0,0 +1,464 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include + +#include "gstmplex.h" + +#include "videostrm.hh" +#include "audiostrm.hh" + + +/* elementfactory information */ +static GstElementDetails gst_mplex_details = { + "MPlex multiplexer", + "Codec/Audio/Decoder", + "GPL", + "multiplex mpeg audio and video into a system stream", + VERSION, + "Wim Taymans ", + "(C) 2002", +}; + +/* Sidec signals and args */ +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +enum { + ARG_0, + ARG_MUX_FORMAT, + ARG_MUX_BITRATE, + ARG_VIDEO_BUFFER, + ARG_SYNC_OFFSET, + ARG_SECTOR_SIZE, + ARG_VBR, + ARG_PACKETS_PER_PACK, + ARG_SYSTEM_HEADERS, + /* FILL ME */ +}; + +GST_PAD_TEMPLATE_FACTORY (src_factory, + "src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_CAPS_NEW ( + "src_video", + "video/mpeg", + "mpegversion", GST_PROPS_INT_RANGE (1, 2), + "systemstream", GST_PROPS_BOOLEAN (TRUE) + ) +) + +GST_PAD_TEMPLATE_FACTORY (video_sink_factory, + "video_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_CAPS_NEW ( + "sink_video", + "video/mpeg", + "mpegversion", GST_PROPS_INT_RANGE (1, 2), + "systemstream", GST_PROPS_BOOLEAN (FALSE) + ) +) + +GST_PAD_TEMPLATE_FACTORY (audio_sink_factory, + "audio_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_CAPS_NEW ( + "sink_audio", + "audio/mp3", + NULL + ) +) + +#define GST_TYPE_MPLEX_MUX_FORMAT (gst_mplex_mux_format_get_type()) +static GType +gst_mplex_mux_format_get_type (void) +{ + static GType mplex_mux_format_type = 0; + static GEnumValue mplex_mux_format[] = { + { MPEG_FORMAT_MPEG1, "0", "Generic MPEG1" }, + { MPEG_FORMAT_VCD, "1", "VCD" }, + { MPEG_FORMAT_VCD_NSR, "2", "user-rate VCD" }, + { MPEG_FORMAT_MPEG2, "3", "Generic MPEG2" }, + { MPEG_FORMAT_SVCD, "4", "SVCD" }, + { MPEG_FORMAT_SVCD_NSR, "5", "user-rate SVCD" }, + { MPEG_FORMAT_VCD_STILL, "6", "VCD Stills" }, + { MPEG_FORMAT_SVCD_STILL, "7", "SVCD Stills" }, + { MPEG_FORMAT_DVD, "8", "DVD" }, + {0, NULL, NULL}, + }; + if (!mplex_mux_format_type) { + mplex_mux_format_type = g_enum_register_static("GstMPlexMuxFormat", mplex_mux_format); + } + return mplex_mux_format_type; +} + +static void gst_mplex_class_init (GstMPlex *klass); +static void gst_mplex_init (GstMPlex *mplex); + +static GstPad* gst_mplex_request_new_pad (GstElement *element, + GstPadTemplate *templ, + const gchar *req_name); +static void gst_mplex_loop (GstElement *element); +static size_t gst_mplex_read_callback (BitStream *bitstream, + uint8_t *dest, size_t size, + void *user_data); +static size_t gst_mplex_write_callback (PS_Stream *stream, + uint8_t *data, size_t size, + void *user_data); + +static void gst_mplex_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); +static void gst_mplex_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); + +static GstElementClass *parent_class = NULL; +//static guint gst_mplex_signals[LAST_SIGNAL] = { 0 }; + +GType +gst_mplex_get_type (void) +{ + static GType mplex_type = 0; + + if (!mplex_type) { + static const GTypeInfo mplex_info = { + sizeof(GstMPlexClass), + NULL, + NULL, + (GClassInitFunc) gst_mplex_class_init, + NULL, + NULL, + sizeof(GstMPlex), + 0, + (GInstanceInitFunc) gst_mplex_init, + NULL + }; + mplex_type = g_type_register_static (GST_TYPE_ELEMENT, "GstMPlex", &mplex_info, (GTypeFlags)0); + } + + return mplex_type; +} + +static void +gst_mplex_class_init (GstMPlex *klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass*)klass; + gstelement_class = (GstElementClass*)klass; + + parent_class = GST_ELEMENT_CLASS (g_type_class_ref (GST_TYPE_ELEMENT)); + + gobject_class->set_property = gst_mplex_set_property; + gobject_class->get_property = gst_mplex_get_property; + + g_object_class_install_property (gobject_class, ARG_MUX_FORMAT, + g_param_spec_enum ("mux_format", "Mux format", "Set defaults for particular MPEG profiles", + GST_TYPE_MPLEX_MUX_FORMAT, MPEG_FORMAT_MPEG1, (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_MUX_BITRATE, + g_param_spec_int ("mux_bitrate", "Mux bitrate", "Specify data rate of output stream in kbit/sec" + "(0 = Compute from source streams)", + 0, G_MAXINT, 0, (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_VIDEO_BUFFER, + g_param_spec_int ("video_buffer", "Video buffer", "Specifies decoder buffers size in kB", + 20, 2000, 20, (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_SYNC_OFFSET, + g_param_spec_int ("sync_offset", "Sync offset", "Specify offset of timestamps (video-audio) in mSec", + 0, G_MAXINT, 0, (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_SECTOR_SIZE, + g_param_spec_int ("sector_size", "Sector size", "Specify sector size in bytes for generic formats", + 256, 16384, 2028, (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_VBR, + g_param_spec_boolean ("vbr", "VBR", "Multiplex variable bit-rate video", + TRUE, (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_PACKETS_PER_PACK, + g_param_spec_int ("packets_per_pack", "Packets per pack", + "Number of packets per pack generic formats", + 1, 100, 1, (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_SYSTEM_HEADERS, + g_param_spec_boolean ("system_headers", "System headers", + " Create System header in every pack in generic formats", + TRUE, (GParamFlags) G_PARAM_READWRITE)); + + gstelement_class->request_new_pad = gst_mplex_request_new_pad; +} + +static void +gst_mplex_init (GstMPlex *mplex) +{ + mplex->srcpad = gst_pad_new_from_template ( + GST_PAD_TEMPLATE_GET (src_factory), "src"); + gst_element_add_pad (GST_ELEMENT (mplex), mplex->srcpad); + + gst_element_set_loop_function (GST_ELEMENT (mplex), gst_mplex_loop); + + mplex->ostrm = new OutputStream (); + mplex->strms = new vector(); + + mplex->state = GST_MPLEX_OPEN_STREAMS; + + mplex->ostrm->opt_mux_format = MPEG_FORMAT_DVD; + + (void)mjpeg_default_handler_verbosity(mplex->ostrm->opt_verbosity); +} + +static GstPad* +gst_mplex_request_new_pad (GstElement *element, + GstPadTemplate *templ, + const gchar *req_name) +{ + GstMPlexStream *stream; + GstMPlex *mplex; + GstPad *pad = NULL; + + mplex = GST_MPLEX (element); + + stream = g_new0 (GstMPlexStream, 1); + + if (!strncmp (templ->name_template, "audio", 5)) { + pad = gst_pad_new ("audio_sink", GST_PAD_SINK); + + stream->type = GST_MPLEX_STREAM_AC3; + } + else if (!strncmp (templ->name_template, "video", 5)) { + pad = gst_pad_new ("video_sink", GST_PAD_SINK); + + stream->type = GST_MPLEX_STREAM_DVD_VIDEO; + } + + if (pad) { + stream->pad = pad; + stream->bitstream = new IBitStream(); + stream->bytestream = gst_bytestream_new (pad); + + mplex->streams = g_list_prepend (mplex->streams, stream); + + gst_element_add_pad (element, pad); + } + + return pad; +} + + +static size_t +gst_mplex_read_callback (BitStream *bitstream, uint8_t *dest, size_t size, void *user_data) +{ + GstMPlexStream *stream; + guint8 *data; + guint32 len; + + stream = (GstMPlexStream *) user_data; + + len = gst_bytestream_peek_bytes (stream->bytestream, &data, size); + if (len < size) { + g_print ("got %d bytes out of %d\n", len, size); + } + + memcpy (dest, data, len); + + gst_bytestream_flush_fast (stream->bytestream, len); + + return len; +} + +static size_t +gst_mplex_write_callback (PS_Stream *stream, uint8_t *data, size_t size, void *user_data) +{ + GstMPlex *mplex; + GstBuffer *outbuf; + + mplex = GST_MPLEX (user_data); + + if (GST_PAD_IS_USABLE (mplex->srcpad)) { + outbuf = gst_buffer_new_and_alloc (size); + memcpy (GST_BUFFER_DATA (outbuf), data, size); + + gst_pad_push (mplex->srcpad, outbuf); + } + + return size; +} + +static void +gst_mplex_loop (GstElement *element) +{ + GstMPlex *mplex; + + mplex = GST_MPLEX (element); + + switch (mplex->state) { + case GST_MPLEX_OPEN_STREAMS: + { + mplex->ostrm->InitSyntaxParameters(); + + GList *walk = mplex->streams; + while (walk) { + GstMPlexStream *stream = (GstMPlexStream *) walk->data; + + stream->bitstream->open (gst_mplex_read_callback, stream); + + switch (stream->type) { + case GST_MPLEX_STREAM_AC3: + { + AC3Stream *ac3stream; + + ac3stream = new AC3Stream(*stream->bitstream, *mplex->ostrm); + ac3stream->Init(0); + stream->elem_stream = ac3stream; + break; + } + case GST_MPLEX_STREAM_DVD_VIDEO: + { + DVDVideoStream *dvdstream; + + dvdstream = new DVDVideoStream(*stream->bitstream, *mplex->ostrm); + dvdstream->Init(0); + stream->elem_stream = dvdstream; + break; + } + default: + break; + } + mplex->strms->push_back(stream->elem_stream); + + walk = g_list_next (walk); + } + + mplex->ps_stream = new PS_Stream (gst_mplex_write_callback, mplex); + mplex->ostrm->Init (mplex->strms, mplex->ps_stream); + + /* move to running state after this */ + mplex->state = GST_MPLEX_RUN; + break; + } + case GST_MPLEX_RUN: + if (!mplex->ostrm->OutputMultiplex()) { + mplex->state = GST_MPLEX_END; + } + break; + case GST_MPLEX_END: + { + mplex->ostrm->Close (); + gst_pad_push (mplex->srcpad, GST_BUFFER (gst_event_new (GST_EVENT_EOS))); + gst_element_set_eos (element); + break; + } + default: + break; + } + +} + +static void +gst_mplex_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstMPlex *mplex; + + mplex = GST_MPLEX(object); + + switch(prop_id) { + case ARG_MUX_FORMAT: + break; + case ARG_MUX_BITRATE: + break; + case ARG_VIDEO_BUFFER: + break; + case ARG_SYNC_OFFSET: + break; + case ARG_SECTOR_SIZE: + break; + case ARG_VBR: + break; + case ARG_PACKETS_PER_PACK: + break; + case ARG_SYSTEM_HEADERS: + break; + default: + //G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } +} + +static void +gst_mplex_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstMPlex *mplex; + + mplex = GST_MPLEX(object); + + switch(prop_id) { + case ARG_MUX_FORMAT: + break; + case ARG_MUX_BITRATE: + break; + case ARG_VIDEO_BUFFER: + break; + case ARG_SYNC_OFFSET: + break; + case ARG_SECTOR_SIZE: + break; + case ARG_VBR: + break; + case ARG_PACKETS_PER_PACK: + break; + case ARG_SYSTEM_HEADERS: + break; + default: + //G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +plugin_init (GModule *module, GstPlugin *plugin) +{ + GstElementFactory *factory; + + /* this filter needs the bytestream package */ + if (!gst_library_load ("gstbytestream")) + return FALSE; + + /* create an elementfactory for the avi_demux element */ + factory = gst_element_factory_new ("mplex",GST_TYPE_MPLEX, + &gst_mplex_details); + g_return_val_if_fail (factory != NULL, FALSE); + gst_element_factory_set_rank (factory, GST_ELEMENT_RANK_PRIMARY); + + gst_element_factory_add_pad_template (factory, GST_PAD_TEMPLATE_GET (src_factory)); + gst_element_factory_add_pad_template (factory, GST_PAD_TEMPLATE_GET (audio_sink_factory)); + gst_element_factory_add_pad_template (factory, GST_PAD_TEMPLATE_GET (video_sink_factory)); + + gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); + + return TRUE; +} + +GstPluginDesc plugin_desc = { + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "mplex", + plugin_init +}; + diff --git a/ext/mplex/gstmplex.h b/ext/mplex/gstmplex.h new file mode 100644 index 000000000..9266eb124 --- /dev/null +++ b/ext/mplex/gstmplex.h @@ -0,0 +1,97 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_MPLEX_H__ +#define __GST_MPLEX_H__ + + +#include +#include + +#include +#include + +#include "outputstream.hh" +#include "bits.hh" + + +G_BEGIN_DECLS + +#define GST_TYPE_MPLEX \ + (gst_mplex_get_type()) +#define GST_MPLEX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MPLEX,GstMPlex)) +#define GST_MPLEX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MPLEX,GstMPlex)) +#define GST_IS_MPLEX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MPLEX)) +#define GST_IS_MPLEX_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MPLEX)) + +typedef enum { + GST_MPLEX_INIT, + GST_MPLEX_OPEN_STREAMS, + GST_MPLEX_RUN, + GST_MPLEX_END +} GstMPlexState; + +typedef enum { + GST_MPLEX_STREAM_UNKOWN, + GST_MPLEX_STREAM_AC3, + GST_MPLEX_STREAM_MPA, + GST_MPLEX_STREAM_LPCM, + GST_MPLEX_STREAM_VIDEO, + GST_MPLEX_STREAM_DVD_VIDEO, +} GstMPlexStreamType; + +typedef struct _GstMPlex GstMPlex; +typedef struct _GstMPlexStream GstMPlexStream; +typedef struct _GstMPlexClass GstMPlexClass; + +struct _GstMPlexStream { + IBitStream *bitstream; + ElementaryStream *elem_stream; + GstPad *pad; + GstMPlexStreamType type; + GstByteStream *bytestream; +}; + +struct _GstMPlex { + GstElement element; + + GstMPlexState state; + /* pads */ + GstPad *srcpad; + GList *streams; + + vector *strms; + OutputStream *ostrm; + PS_Stream *ps_stream; +}; + +struct _GstMPlexClass { + GstElementClass parent_class; +}; + +GType gst_mplex_get_type (void); + +G_END_DECLS + +#endif /* __GST_MPLEX_H__ */ diff --git a/ext/mplex/inputstrm.cc b/ext/mplex/inputstrm.cc new file mode 100644 index 000000000..4a19724a5 --- /dev/null +++ b/ext/mplex/inputstrm.cc @@ -0,0 +1,249 @@ + +/* + * inputstrm.c: Base classes related to muxing out input streams into + * the output stream. + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include +#include +#include "fastintfns.h" +#include "inputstrm.hh" +#include "outputstream.hh" + +MuxStream::MuxStream ():init (false) +{ +} + + +void +MuxStream::Init (const int strm_id, + const unsigned int _buf_scale, + const unsigned int buf_size, + const unsigned int _zero_stuffing, bool bufs_in_first, bool always_bufs) +{ + stream_id = strm_id; + nsec = 0; + zero_stuffing = _zero_stuffing; + buffer_scale = _buf_scale; + buffer_size = buf_size; + bufmodel.Init (buf_size); + buffers_in_header = bufs_in_first; + always_buffers_in_header = always_bufs; + new_au_next_sec = true; + init = true; +} + + + +unsigned int +MuxStream::BufferSizeCode () +{ + if (buffer_scale == 1) + return buffer_size / 1024; + else if (buffer_scale == 0) + return buffer_size / 128; + else + assert (false); +} + + + +ElementaryStream::ElementaryStream (IBitStream & ibs, OutputStream & into, stream_kind _kind): +InputStream (ibs), muxinto (into), kind (_kind), buffer_min (INT_MAX), buffer_max (1) +{ +} + + +bool ElementaryStream::NextAU () +{ + Aunit * + p_au = + next (); + + if (p_au != NULL) { + au = p_au; + au_unsent = p_au->length; + return true; + } else { + au_unsent = 0; + return false; + } +} + + +Aunit * +ElementaryStream::Lookahead () +{ + return aunits.lookahead (); +} + +unsigned int +ElementaryStream::BytesToMuxAUEnd (unsigned int sector_transport_size) +{ + return (au_unsent / min_packet_data) * sector_transport_size + + (au_unsent % min_packet_data) + (sector_transport_size - min_packet_data); +} + + +/****************************************************************** + * ElementaryStream::ReadPacketPayload + * + * Reads the stream data from actual input stream, updates decode + * buffer model and current access unit information from the + * look-ahead scanning buffer to account for bytes_muxed bytes being + * muxed out. Particular important is the maintenance of "au_unsent" + * the count of how much data in the current AU remains umuxed. It + * not only allows us to keep track of AU's but is also used for + * generating substream headers + * + * Unless we need to over-ride it to handle sub-stream headers + * The packet payload for an elementary stream is simply the parsed and + * spliced buffered stream data.. + * + ******************************************************************/ + + + +unsigned int +ElementaryStream::ReadPacketPayload (uint8_t * dst, unsigned int to_read) +{ + unsigned int actually_read = bs.read_buffered_bytes (dst, to_read); + + Muxed (actually_read); + return actually_read; +} + + + + +void +ElementaryStream::Muxed (unsigned int bytes_muxed) +{ + clockticks decode_time; + + if (bytes_muxed == 0 || MuxCompleted ()) + return; + + + /* Work through what's left of the current AU and the following AU's + updating the info until we reach a point where an AU had to be + split between packets. + NOTE: It *is* possible for this loop to iterate. + + The DTS/PTS field for the packet in this case would have been + given the that for the first AU to start in the packet. + Whether Joe-Blow's hardware VCD player handles this properly is + another matter of course! + */ + + decode_time = RequiredDTS (); + while (au_unsent < bytes_muxed) { + + bufmodel.Queued (au_unsent, decode_time); + bytes_muxed -= au_unsent; + if (!NextAU ()) + return; + new_au_next_sec = true; + decode_time = RequiredDTS (); + }; + + // We've now reached a point where the current AU overran or + // fitted exactly. We need to distinguish the latter case + // so we can record whether the next packet starts with an + // existing AU or not - info we need to decide what PTS/DTS + // info to write at the start of the next packet. + + if (au_unsent > bytes_muxed) { + + bufmodel.Queued (bytes_muxed, decode_time); + au_unsent -= bytes_muxed; + new_au_next_sec = false; + } else // if (au_unsent == bytes_muxed) + { + bufmodel.Queued (bytes_muxed, decode_time); + if (!NextAU ()) + return; + new_au_next_sec = true; + } + +} + +bool +ElementaryStream::MuxPossible (clockticks currentSCR) +{ + return (!RunOutComplete () && bufmodel.Space () > max_packet_data); +} + + +void +ElementaryStream::UpdateBufferMinMax () +{ + buffer_min = buffer_min < (int) bufmodel.Space ()? buffer_min : bufmodel.Space (); + buffer_max = buffer_max > (int) bufmodel.Space ()? buffer_max : bufmodel.Space (); +} + + + +void +ElementaryStream::AllDemuxed () +{ + bufmodel.Flushed (); +} + +void +ElementaryStream::DemuxedTo (clockticks SCR) +{ + bufmodel.Cleaned (SCR); +} + +bool +ElementaryStream::MuxCompleted () +{ + return au_unsent == 0; +} + +void +ElementaryStream::SetSyncOffset (clockticks sync_offset) +{ + timestamp_delay = sync_offset; +} + +Aunit * +ElementaryStream::next () +{ + Aunit *res; + + while (AUBufferNeedsRefill ()) { + FillAUbuffer (FRAME_CHUNK); + } + res = aunits.next (); + return res; +} + + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/inputstrm.hh b/ext/mplex/inputstrm.hh new file mode 100644 index 000000000..a42474959 --- /dev/null +++ b/ext/mplex/inputstrm.hh @@ -0,0 +1,278 @@ + +/* + * inptstrm.hh: Input stream classes for MPEG multiplexing + * TODO: Split into the base classes and the different types of + * actual input stream. + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __INPUTSTRM_H__ +#define __INPUTSTRM_H__ + +#include +#include +#include +#include + +#include "mjpeg_types.h" +#include "mpegconsts.h" +#include "format_codes.h" +#include "mjpeg_logging.h" + +#include "mplexconsts.hh" +#include "bits.hh" +#include "aunit.hh" +#include "vector.hh" +#include "buffer.hh" + + +class InputStream +{ +public: + InputStream (IBitStream & istream):bs (istream), + eoscan (false), stream_length (0), last_buffered_AU (0), decoding_order (0), old_frames (0) + { + } + + void SetBufSize (unsigned int buf_size) + { + bs.SetBufSize (buf_size); + } + +protected: + IBitStream & bs; + bool eoscan; + bitcount_t stream_length; + off_t file_length; + + unsigned int last_buffered_AU; // decode seq num of last buffered frame + 1 + bitcount_t AU_start; + uint32_t syncword; + bitcount_t prev_offset; + unsigned int decoding_order; + unsigned int old_frames; + +}; + +// +// Abstract forward reference... +// + +class OutputStream; + + +class MuxStream +{ +public: + MuxStream (); + + void Init (const int strm_id, + const unsigned int _buf_scale, + const unsigned int buf_size, + const unsigned int _zero_stuffing, const bool bufs_in_first, const bool always_bufs); + + unsigned int BufferSizeCode (); + inline unsigned int BufferSize () + { + return buffer_size; + } + inline unsigned int BufferScale () + { + return buffer_scale; + } + + + inline void SetMaxPacketData (unsigned int max) + { + max_packet_data = max; + } + inline void SetMinPacketData (unsigned int min) + { + min_packet_data = min; + } + inline unsigned int MaxPacketData () + { + return max_packet_data; + } + inline unsigned int MinPacketData () + { + return min_packet_data; + } + inline bool NewAUNextSector () + { + return new_au_next_sec; + } + + // + // Read the next packet payload (sub-stream headers plus + // parsed and spliced stream data) for a packet with the + // specified payload capacity. Update the AU info. + // + + virtual unsigned int ReadPacketPayload (uint8_t * dst, unsigned int to_read) = 0; + + // + // Return the size of the substream headers... + // + virtual unsigned int StreamHeaderSize () + { + return 0; + } + +public: // TODO should go protected once encapsulation complete + int stream_id; + unsigned int buffer_scale; + unsigned int buffer_size; + BufferModel bufmodel; + unsigned int max_packet_data; + unsigned int min_packet_data; + unsigned int zero_stuffing; + unsigned int nsec; + bool buffers_in_header; + bool always_buffers_in_header; + bool new_au_next_sec; + bool init; +}; + +class DummyMuxStream:public MuxStream +{ +public: + DummyMuxStream (const int strm_id, const unsigned int buf_scale, unsigned int buf_size) + { + stream_id = strm_id; + buffer_scale = buf_scale; + buffer_size = buf_size; + } + + unsigned int ReadPacketPayload (uint8_t * dst, unsigned int to_read) + { + abort (); + return 0; + } +}; + + +class ElementaryStream:public InputStream, public MuxStream +{ +public: + enum stream_kind + { audio, video, dummy }; + ElementaryStream (IBitStream & ibs, OutputStream & into, stream_kind kind); + virtual void Close () = 0; + + bool NextAU (); + Aunit *Lookahead (); + unsigned int BytesToMuxAUEnd (unsigned int sector_transport_size); + bool MuxCompleted (); + virtual bool MuxPossible (clockticks currentSCR); + void DemuxedTo (clockticks SCR); + void SetTSOffset (clockticks baseTS); + void AllDemuxed (); + inline stream_kind Kind () + { + return kind; + } + inline int BufferMin () + { + return buffer_min; + } + inline int BufferMax () + { + return buffer_max; + } + inline clockticks RequiredDTS () + { + return au->DTS + timestamp_delay; + }; + inline clockticks RequiredPTS () + { + return au->PTS + timestamp_delay; + }; + inline clockticks NextRequiredDTS () + { + Aunit *next = Lookahead (); + + if (next != 0) + return next->DTS + timestamp_delay; + else + return 0; + }; + inline clockticks NextRequiredPTS () + { + Aunit *next = Lookahead (); + + if (next != 0) + return next->PTS + timestamp_delay; + else + return 0; + }; + + void UpdateBufferMinMax (); + + void SetSyncOffset (clockticks timestamp_delay); + + + inline bool BuffersInHeader () + { + return buffers_in_header; + } + virtual unsigned int NominalBitRate () = 0; + virtual bool RunOutComplete () = 0; + virtual void OutputSector () = 0; + + virtual unsigned int ReadPacketPayload (uint8_t * dst, unsigned int to_read); + + +protected: + virtual void FillAUbuffer (unsigned int frames_to_buffer) = 0; + virtual void InitAUbuffer () = 0; + virtual bool AUBufferNeedsRefill () = 0; + AUStream aunits; + void Muxed (unsigned int bytes_muxed); + +public: // TODO should go protected once encapsulation complete + // N.b. currently length=0 is used to indicate an ended + // stream. + // au itself should simply disappear + Aunit * au; + clockticks timestamp_delay; + +protected: + unsigned int au_unsent; + Aunit *next (); + + OutputStream & muxinto; + stream_kind kind; + int buffer_min; + int buffer_max; + int FRAME_CHUNK; + +}; + + + +#endif // __INPUTSTRM_H__ + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/interact.cc b/ext/mplex/interact.cc new file mode 100644 index 000000000..2191d552c --- /dev/null +++ b/ext/mplex/interact.cc @@ -0,0 +1,196 @@ +#include +#include +#include +#ifdef HAVE_GETOPT_H +#include +#endif +#include + +#include +#include + +#include "interact.hh" +#include "videostrm.hh" +#include "audiostrm.hh" +#include "mplexconsts.hh" + + +#if 0 +int opt_verbosity = 1; +int opt_buffer_size = 46; +int opt_data_rate = 0; /* 3486 = 174300B/sec would be right for VCD */ +int opt_video_offset = 0; +int opt_audio_offset = 0; +int opt_sector_size = 2324; +int opt_VBR = 0; +int opt_mpeg = 1; +int opt_mux_format = 0; /* Generic MPEG-1 stream as default */ +int opt_multifile_segment = 0; +int opt_always_system_headers = 0; +int opt_packets_per_pack = 20; +bool opt_ignore_underrun = false; +off_t opt_max_segment_size = 0; + +/************************************************************************* + Startbildschirm und Anzahl der Argumente + + Intro Screen and argument check +*************************************************************************/ + +static void +Usage (char *str) +{ + fprintf (stderr, + "mjpegtools mplex version " VERSION "\n" + "Usage: %s [params] -o ... \n" + " %%d in the output file name is by segment count\n" + " where possible params are:\n" + "--verbose|-v num\n" + " Level of verbosity. 0 = quiet, 1 = normal 2 = verbose/debug\n" + "--format|-f fmt\n" + " Set defaults for particular MPEG profiles\n" + " [0 = Generic MPEG1, 1 = VCD, 2 = user-rate VCD, 3 = Generic MPEG2,\n" + " 4 = SVCD, 5 = user-rate SVCD\n" + " 6 = VCD Stills, 7 = SVCD Stills, 8 = DVD]\n" + "--mux-bitrate|-r num\n" + " Specify data rate of output stream in kbit/sec\n" + " (default 0=Compute from source streams)\n" + "--video-buffer|-b num\n" + " Specifies decoder buffers size in kB. [ 20...2000]\n" + "--mux-limit|-l num\n" + " Multiplex only num seconds of material (default 0=multiplex all)\n" + "--sync-offset|-O num\n" + " Specify offset of timestamps (video-audio) in mSec\n" + "--sector-size|-s num\n" + " Specify sector size in bytes for generic formats [256..16384]\n" + "--vbr|-V\n" + " Multiplex variable bit-rate video\n" + "--packets-per-pack|-p num\n" + " Number of packets per pack generic formats [1..100]\n" + "--system-headers|-h\n" + " Create System header in every pack in generic formats\n" + "--max-segment-size|-S size\n" + " Maximum size of output file(s) in Mbyte (default: 2000) (0 = no limit)\n" + "--split-segment|-M\n" + " Simply split a sequence across files rather than building run-out/run-in\n" + "--help|-?\n" " Print this lot out!\n", str); + exit (1); +} + +static const char short_options[] = "o:b:r:O:v:m:f:l:s:S:q:p:VXMeh"; + +#if defined(HAVE_GETOPT_LONG) +static struct option long_options[] = { + {"verbose", 1, 0, 'v'}, + {"format", 1, 0, 'f'}, + {"mux-bitrate", 1, 0, 'r'}, + {"video-buffer", 1, 0, 'b'}, + {"output", 1, 0, 'o'}, + {"sync-offset", 1, 0, 'O'}, + {"vbr", 1, 0, 'V'}, + {"system-headers", 1, 0, 'h'}, + {"split-segment", 0, &opt_multifile_segment, 1}, + {"max-segment-size", 1, 0, 'S'}, + {"mux-upto", 1, 0, 'l'}, + {"packets-per-pack", 1, 0, 'p'}, + {"sector-size", 1, 0, 's'}, + {"help", 0, 0, '?'}, + {0, 0, 0, 0} +}; +#endif + +int +intro_and_options (int argc, char *argv[], char **multplex_outfile) +{ + int n; + +#if defined(HAVE_GETOPT_LONG) + while ((n = getopt_long (argc, argv, short_options, long_options, NULL)) != -1) +#else + while ((n = getopt (argc, argv, short_options)) != -1) +#endif + { + switch (n) { + case 0: + break; + case 'm': + opt_mpeg = atoi (optarg); + if (opt_mpeg < 1 || opt_mpeg > 2) + Usage (argv[0]); + + break; + case 'v': + opt_verbosity = atoi (optarg); + if (opt_verbosity < 0 || opt_verbosity > 2) + Usage (argv[0]); + break; + + case 'V': + opt_VBR = 1; + break; + + case 'h': + opt_always_system_headers = 1; + break; + + case 'b': + opt_buffer_size = atoi (optarg); + if (opt_buffer_size < 0 || opt_buffer_size > 1000) + Usage (argv[0]); + break; + + case 'r': + opt_data_rate = atoi (optarg); + if (opt_data_rate < 0) + Usage (argv[0]); + /* Convert from kbit/sec (user spec) to 50B/sec units... */ + opt_data_rate = ((opt_data_rate * 1000 / 8 + 49) / 50) * 50; + break; + + case 'O': + opt_video_offset = atoi (optarg); + if (opt_video_offset < 0) { + opt_audio_offset = -opt_video_offset; + opt_video_offset = 0; + } + break; + + case 'p': + opt_packets_per_pack = atoi (optarg); + if (opt_packets_per_pack < 1 || opt_packets_per_pack > 100) + Usage (argv[0]); + break; + + + case 'f': + opt_mux_format = atoi (optarg); + if (opt_mux_format != MPEG_FORMAT_DVD && + (opt_mux_format < MPEG_FORMAT_MPEG1 || opt_mux_format > MPEG_FORMAT_LAST) + ) + Usage (argv[0]); + break; + case 's': + opt_sector_size = atoi (optarg); + if (opt_sector_size < 256 || opt_sector_size > 16384) + Usage (argv[0]); + break; + case 'S': + opt_max_segment_size = atoi (optarg); + if (opt_max_segment_size < 0) + Usage (argv[0]); + opt_max_segment_size *= 1024 * 1024; + break; + case 'M': + opt_multifile_segment = 1; + break; + case '?': + default: + Usage (argv[0]); + break; + } + } + (void) mjpeg_default_handler_verbosity (opt_verbosity); + mjpeg_info ("mplex version %s (%s)", MPLEX_VER, MPLEX_DATE); + return optind - 1; +} +#endif diff --git a/ext/mplex/lpcmstrm_in.cc b/ext/mplex/lpcmstrm_in.cc new file mode 100644 index 000000000..2e73a625c --- /dev/null +++ b/ext/mplex/lpcmstrm_in.cc @@ -0,0 +1,303 @@ +/* + * lpcmstrm_in.c: LPCM Audio strem class members handling scanning and + * buffering raw input stream. + * + * Copyright (C) 2001 Andrew Stevens + * Copyright (C) 2000,2001 Brent Byeler for original header-structure + * parsing code. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include "audiostrm.hh" +#include "outputstream.hh" + + + + +LPCMStream::LPCMStream (IBitStream & ibs, OutputStream & into): +AudioStream (ibs, into) +{ +} + +bool LPCMStream::Probe (IBitStream & bs) +{ + return true; +} + + +/************************************************************************* + * + * Reads initial stream parameters and displays feedback banner to users + * + *************************************************************************/ + + +void +LPCMStream::Init (const int stream_num) +{ + + MuxStream::Init (PRIVATE_STR_1, 1, // Buffer scale + default_buffer_size, + muxinto.vcd_zero_stuffing, + muxinto.buffers_in_audio, muxinto.always_buffers_in_audio); + mjpeg_info ("Scanning for header info: LPCM Audio stream %02x", stream_num); + + InitAUbuffer (); + + AU_start = bs.bitcount (); + + // This is a dummy debug version that simply assumes 48kHz + // two channel 16 bit sample LPCM + samples_per_second = 48000; + channels = 2; + bits_per_sample = 16; + bytes_per_frame = + samples_per_second * channels * bits_per_sample / 8 * ticks_per_frame_90kHz / 90000; + frame_index = 0; + dynamic_range_code = 0x80; + + /* Presentation/decoding time-stamping */ + access_unit.start = AU_start; + access_unit.length = bytes_per_frame; + access_unit.PTS = static_cast < clockticks > (decoding_order) * + (CLOCKS_per_90Kth_sec * ticks_per_frame_90kHz); + access_unit.DTS = access_unit.PTS; + access_unit.dorder = decoding_order; + decoding_order++; + aunits.append (access_unit); + + OutputHdrInfo (); +} + +unsigned int +LPCMStream::NominalBitRate () +{ + return samples_per_second * channels * bits_per_sample; +} + + + +void +LPCMStream::FillAUbuffer (unsigned int frames_to_buffer) +{ + last_buffered_AU += frames_to_buffer; + mjpeg_debug ("Scanning %d MPEG LPCM audio frames to frame %d", + frames_to_buffer, last_buffered_AU); + + static int header_skip = 0; // Initially skipped past 5 bytes of header + int skip; + bool bad_last_frame = false; + + while (!bs.eos () && + decoding_order < last_buffered_AU) { + skip = access_unit.length - header_skip; + mjpeg_debug ("Buffering frame %d (%d bytes)\n", decoding_order - 1, skip); + if (skip & 0x1) + bs.getbits (8); + if (skip & 0x2) + bs.getbits (16); + skip = skip >> 2; + + for (int i = 0; i < skip; i++) { + bs.getbits (32); + } + + prev_offset = AU_start; + AU_start = bs.bitcount (); + if (AU_start - prev_offset != access_unit.length * 8) { + bad_last_frame = true; + break; + } + // Here we would check for header data but LPCM has no headers... + if (bs.eos ()) + break; + + access_unit.start = AU_start; + access_unit.length = bytes_per_frame; + access_unit.PTS = static_cast < clockticks > (decoding_order) * + (CLOCKS_per_90Kth_sec * ticks_per_frame_90kHz); + access_unit.DTS = access_unit.PTS; + access_unit.dorder = decoding_order; + decoding_order++; + aunits.append (access_unit); + num_frames[0]++; + + num_syncword++; + + if (num_syncword >= old_frames + 10) { + mjpeg_debug ("Got %d frame headers.", num_syncword); + old_frames = num_syncword; + } + mjpeg_debug ("Got frame %d\n", decoding_order); + + } + if (bad_last_frame) { + mjpeg_error_exit1 ("Last LPCM frame ended prematurely!\n"); + } + last_buffered_AU = decoding_order; + eoscan = bs.eos (); + +} + + + +void +LPCMStream::Close () +{ + stream_length = AU_start / 8; + mjpeg_info ("AUDIO_STATISTICS: %02x", stream_id); + mjpeg_info ("Audio stream length %lld bytes.", stream_length); + mjpeg_info ("Frames : %8u ", num_frames[0]); + bs.close (); +} + +/************************************************************************* + OutputAudioInfo + gibt gesammelte Informationen zu den Audio Access Units aus. + + Prints information on audio access units +*************************************************************************/ + +void +LPCMStream::OutputHdrInfo () +{ + mjpeg_info ("LPCM AUDIO STREAM:"); + + mjpeg_info ("Bit rate : %8u bytes/sec (%3u kbit/sec)", + NominalBitRate () / 8, NominalBitRate ()); + mjpeg_info ("Channels : %d\n", channels); + mjpeg_info ("Bits per sample: %d\n", bits_per_sample); + mjpeg_info ("Frequency : %d Hz", samples_per_second); + +} + + +unsigned int +LPCMStream::ReadPacketPayload (uint8_t * dst, unsigned int to_read) +{ + unsigned int header_size = LPCMStream::StreamHeaderSize (); + unsigned int bytes_read = bs.read_buffered_bytes (dst + header_size, + to_read - header_size); + clockticks decode_time; + bool starting_frame_found = false; + uint8_t starting_frame_index = 0; + + int starting_frame_offset = (new_au_next_sec || au_unsent > bytes_read) + ? 0 : au_unsent; + + unsigned int frames = 0; + unsigned int bytes_muxed = bytes_read; + + if (bytes_muxed == 0 || MuxCompleted ()) { + goto completion; + } + + + /* Work through what's left of the current frames and the + following frames's updating the info until we reach a point where + an frame had to be split between packets. + + The DTS/PTS field for the packet in this case would have been + given the that for the first AU to start in the packet. + + */ + + decode_time = RequiredDTS (); + while (au_unsent < bytes_muxed) { + assert (bytes_muxed > 1); + bufmodel.Queued (au_unsent, decode_time); + bytes_muxed -= au_unsent; + if (new_au_next_sec) { + ++frames; + if (!starting_frame_found) { + starting_frame_index = static_cast < uint8_t > (au->dorder % 20); + starting_frame_found = true; + } + } + if (!NextAU ()) { + goto completion; + } + new_au_next_sec = true; + decode_time = RequiredDTS (); + }; + + // We've now reached a point where the current AU overran or + // fitted exactly. We need to distinguish the latter case so we + // can record whether the next packet starts with the tail end of + // // an already started frame or a new one. We need this info to + // decide what PTS/DTS info to write at the start of the next + // packet. + + if (au_unsent > bytes_muxed) { + if (new_au_next_sec) + ++frames; + bufmodel.Queued (bytes_muxed, decode_time); + au_unsent -= bytes_muxed; + new_au_next_sec = false; + } else // if (au_unsent == bytes_muxed) + { + bufmodel.Queued (bytes_muxed, decode_time); + if (new_au_next_sec) + ++frames; + new_au_next_sec = NextAU (); + } +completion: + // Generate the LPCM header... + // Note the index counts from the low byte of the offset so + // the smallest value is 1! + dst[0] = LPCM_SUB_STR_0 + stream_num; + dst[1] = frames; + dst[2] = (starting_frame_offset + 1) >> 8; + dst[3] = (starting_frame_offset + 1) & 0xff; + unsigned int bps_code; + + switch (bits_per_sample) { + case 16: + bps_code = 0; + break; + case 20: + bps_code = 1; + break; + case 24: + bps_code = 2; + break; + default: + bps_code = 3; + break; + } + dst[4] = starting_frame_index; + unsigned int bsf_code = (samples_per_second == 48000) ? 0 : 1; + unsigned int channels_code = channels - 1; + + dst[5] = (bps_code << 6) | (bsf_code << 4) | channels_code; + dst[6] = dynamic_range_code; + return bytes_read + header_size; +} + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/mjpeg_logging.cc b/ext/mplex/mjpeg_logging.cc new file mode 100644 index 000000000..db4b0c07a --- /dev/null +++ b/ext/mplex/mjpeg_logging.cc @@ -0,0 +1,239 @@ +/* + $Id$ + + Copyright (C) 2000 Herbert Valerio Riedel + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +extern int fred; + +#include "mjpeg_logging.h" + +static const char _rcsid[] = "$Id: "; + +#define MAX_DEFAULT_ID_SIZE 16 +#define DEFAULT_DEFAULT_ID "???" + +#ifdef HAVE___PROGNAME +extern const char *__progname; +#endif + +static log_level_t mjpeg_log_verbosity = (log_level_t) 0; +static char default_handler_id[MAX_DEFAULT_ID_SIZE]; +static char default_handler_id_is_set = 0; + +static int +default_mjpeg_log_filter (log_level_t level) +{ + int verb_from_env; + + if (mjpeg_log_verbosity == 0) { + char *mjpeg_verb_env = getenv ("MJPEG_VERBOSITY"); + + if (mjpeg_verb_env != NULL) { + verb_from_env = LOG_WARN - atoi (mjpeg_verb_env); + if (verb_from_env >= LOG_DEBUG && verb_from_env <= LOG_ERROR) + mjpeg_log_verbosity = (log_level_t) verb_from_env; + } + } + return (level < LOG_WARN && level < mjpeg_log_verbosity); +} + +static mjpeg_log_filter_t _filter = default_mjpeg_log_filter; + +static void +default_mjpeg_log_handler (log_level_t level, const char message[]) +{ + const char *ids; + + if ((*_filter) (level)) + return; + if (default_handler_id_is_set) { + ids = default_handler_id; + } else { +#ifdef HAVE___PROGNAME + ids = __progname; +#else + ids = DEFAULT_DEFAULT_ID; +#endif + } + switch (level) { + case LOG_ERROR: + fprintf (stderr, "**ERROR: [%s] %s\n", ids, message); + break; + case LOG_DEBUG: + fprintf (stderr, "--DEBUG: [%s] %s\n", ids, message); + break; + case LOG_WARN: + fprintf (stderr, "++ WARN: [%s] %s\n", ids, message); + break; + case LOG_INFO: + fprintf (stderr, " INFO: [%s] %s\n", ids, message); + break; + default: + assert (0); + } +} + +static mjpeg_log_handler_t _handler = default_mjpeg_log_handler; + + +mjpeg_log_handler_t +mjpeg_log_set_handler (mjpeg_log_handler_t new_handler) +{ + mjpeg_log_handler_t old_handler = _handler; + + _handler = new_handler; + + return old_handler; +} + +/*************** + * + * Set default log handlers degree of verboseity. + * 0 = quiet, 1 = info, 2 = debug + * + *************/ + +int +mjpeg_default_handler_verbosity (int verbosity) +{ + int prev_verb = mjpeg_log_verbosity; + + mjpeg_log_verbosity = (log_level_t) (LOG_WARN - (log_level_t) verbosity); + return prev_verb; +} + +/* + * Set identifier string used by default handler + * + */ +int +mjpeg_default_handler_identifier (const char *new_id) +{ + const char *s; + + if (new_id == NULL) { + default_handler_id_is_set = 0; + return 0; + } + /* find basename of new_id (remove any directory prefix) */ + if ((s = strrchr (new_id, '/')) == NULL) + s = new_id; + else + s = s + 1; + strncpy (default_handler_id, s, MAX_DEFAULT_ID_SIZE); + default_handler_id[MAX_DEFAULT_ID_SIZE - 1] = '\0'; + default_handler_id_is_set = 1; + return 0; +} + + +static void +mjpeg_logv (log_level_t level, const char format[], va_list args) +{ + char buf[1024] = { 0, }; + + /* TODO: Original had a re-entrancy error trap to assist bug + finding. To make this work with multi-threaded applications a + lock is needed hence delete. + */ + + + vsnprintf (buf, sizeof (buf) - 1, format, args); + + _handler (level, buf); +} + +void +mjpeg_log (log_level_t level, const char format[], ...) +{ + va_list args; + + va_start (args, format); + mjpeg_logv (level, format, args); + va_end (args); +} + +void +mjpeg_debug (const char format[], ...) +{ + va_list args; + + va_start (args, format); + mjpeg_logv (LOG_DEBUG, format, args); + va_end (args); +} + +void +mjpeg_info (const char format[], ...) +{ + va_list args; + + va_start (args, format); + mjpeg_logv (LOG_INFO, format, args); + va_end (args); +} + +void +mjpeg_warn (const char format[], ...) +{ + va_list args; + + va_start (args, format); + mjpeg_logv (LOG_WARN, format, args); + va_end (args); +} + +void +mjpeg_error (const char format[], ...) +{ + va_list args; + + va_start (args, format); + mjpeg_logv (LOG_ERROR, format, args); + va_end (args); +} + +void +mjpeg_error_exit1 (const char format[], ...) +{ + va_list args; + + va_start (args, format); + mjpeg_logv (LOG_ERROR, format, args); + va_end (args); + exit (EXIT_FAILURE); +} + + +/* + * Local variables: + * c-file-style: "gnu" + * tab-width: 8 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/mjpeg_logging.h b/ext/mplex/mjpeg_logging.h new file mode 100644 index 000000000..3028f8972 --- /dev/null +++ b/ext/mplex/mjpeg_logging.h @@ -0,0 +1,79 @@ +/* + $Id$ + + Copyright (C) 2000 Herbert Valerio Riedel + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __MJPEG_LOGGING_H__ +#define __MJPEG_LOGGING_H__ + +#include "mjpeg_types.h" + +typedef enum { + LOG_DEBUG = 1, + LOG_INFO, + LOG_WARN, + LOG_ERROR +} log_level_t; + +#ifdef __cplusplus +extern "C" { +#endif +void +mjpeg_log(log_level_t level, const char format[], ...) GNUC_PRINTF(2, 3); + +typedef int(*mjpeg_log_filter_t)(log_level_t level); + +typedef void(*mjpeg_log_handler_t)(log_level_t level, const char message[]); + +mjpeg_log_handler_t +mjpeg_log_set_handler(mjpeg_log_handler_t new_handler); + +int +mjpeg_default_handler_identifier(const char *new_id); + +int +mjpeg_default_handler_verbosity(int verbosity); + +void +mjpeg_debug(const char format[], ...) GNUC_PRINTF(1,2); + +void +mjpeg_info(const char format[], ...) GNUC_PRINTF(1,2); + +void +mjpeg_warn(const char format[], ...) GNUC_PRINTF(1,2); + +void +mjpeg_error(const char format[], ...) GNUC_PRINTF(1,2); + +void +mjpeg_error_exit1(const char format[], ...) GNUC_PRINTF(1,2); + +#ifdef __cplusplus +} +#endif +#endif /* __MJPEG_LOGGING_H__ */ + + +/* + * Local variables: + * c-file-style: "gnu" + * tab-width: 8 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/mjpeg_types.h b/ext/mplex/mjpeg_types.h new file mode 100644 index 000000000..21edb9ac8 --- /dev/null +++ b/ext/mplex/mjpeg_types.h @@ -0,0 +1,120 @@ +/* + $Id$ + + Copyright (C) 2000 Herbert Valerio Riedel + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __MJPEG_TYPES_H__ +#define __MJPEG_TYPES_H__ +//#include + +#if defined(HAVE_STDINT_H) +# include +#elif defined(HAVE_INTTYPES_H) +# include +#elif defined(__CYGWIN__) +# include +typedef u_int8_t uint8_t; +typedef u_int16_t uint16_t; +typedef u_int32_t uint32_t; +typedef u_int64_t uint64_t; +# define INT8_C(c) c +# define INT16_C(c) c +# define INT32_C(c) c +# define INT64_C(c) c ## LL +# define UINT8_C(c) c ## U +# define UINT16_C(c) c ## U +# define UINT32_C(c) c ## U +# define UINT64_C(c) c ## ULL +#else +/* warning ISO/IEC 9899:1999 was missing and even */ +/* fixme */ +/* (Ronald) we'll just give an error now...Better solutions might come later */ +#error You don't seem to have sys/types.h, inttypes.h or stdint.h! \ +This might mean two things: \ +Either you really don't have them, in which case you should \ +install the system headers and/or C-library headers. \ +You might also have forgotten to define whether you have them. \ +You can do this by either defining their presence before including \ +mjpegtools' header files (e.g. "#define HAVE_STDINT_H"), or you can check \ +for their presence in a configure script. mjpegtools' configure \ +script is a good example of how to do this. You need to check for \ +PRId64, stdbool.h, inttypes.h, stdint.h and sys/types.h +#endif /* HAVE_STDINT_H */ + +#if defined(__FreeBSD__) +#include /* FreeBSD - ssize_t */ +#endif + +#if defined(HAVE_STDBOOL_H) && !defined(__cplusplus) +#include +#else +/* ISO/IEC 9899:1999 missing -- enabling workaround */ + +# ifndef __cplusplus +typedef enum + { + false = 0, + true = 1 + } locBool; + +# define false false +# define true true +# define bool locBool +# endif +#endif + +#ifndef PRId64 +#define PRId64 PRID64_STRING_FORMAT +#endif + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) +#define GNUC_PRINTF( format_idx, arg_idx ) \ + __attribute__((format (printf, format_idx, arg_idx))) +#define GNUC_SCANF( format_idx, arg_idx ) \ + __attribute__((format (scanf, format_idx, arg_idx))) +#define GNUC_FORMAT( arg_idx ) \ + __attribute__((format_arg (arg_idx))) +#define GNUC_NORETURN \ + __attribute__((noreturn)) +#define GNUC_CONST \ + __attribute__((const)) +#define GNUC_UNUSED \ + __attribute__((unused)) +#define GNUC_PACKED \ + __attribute__((packed)) +#else /* !__GNUC__ */ +#define GNUC_PRINTF( format_idx, arg_idx ) +#define GNUC_SCANF( format_idx, arg_idx ) +#define GNUC_FORMAT( arg_idx ) +#define GNUC_NORETURN +#define GNUC_CONST +#define GNUC_UNUSED +#define GNUC_PACKED +#endif /* !__GNUC__ */ + + +#endif /* __MJPEG_TYPES_H__ */ + + +/* + * Local variables: + * c-file-style: "gnu" + * tab-width: 8 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/mpastrm_in.cc b/ext/mplex/mpastrm_in.cc new file mode 100644 index 000000000..ea7c32fb1 --- /dev/null +++ b/ext/mplex/mpastrm_in.cc @@ -0,0 +1,329 @@ +/* + * audiostrm_in.c: MPEG Audio strem class members handling scanning + * and buffering raw input stream. + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include "audiostrm.hh" +#include "outputstream.hh" + + +static const char *mpa_audio_version[4] = { + "2.5", + "2.0", + "reserved", + "1.0" +}; + +static const unsigned int mpa_bitrates_kbps[4][3][16] = { + { /* MPEG audio V2.5 */ + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0} + }, + { /*RESERVED*/ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + }, + { /* MPEG audio V2 */ + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0} + }, + { /* MPEG audio V1 */ + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0} + } + +}; + + +static const int mpa_freq_table[4][4] = { + /* MPEG audio V2.5 */ + {11025, 12000, 8000, 0}, + /* RESERVED */ + {0, 0, 0, 0}, + /* MPEG audio V2 */ + {22050, 24000, 16000, 0}, + /* MPEG audio V1 */ + {44100, 48000, 32000, 0} +}; + +static const char mpa_stereo_mode[4][15] = + { "stereo", "joint stereo", "dual channel", "single channel" }; +static const char mpa_copyright_status[2][20] = { "no copyright", "copyright protected" }; +static const char mpa_original_bit[2][10] = { "copy", "original" }; +static const char mpa_emphasis_mode[4][20] = + { "none", "50/15 microseconds", "reserved", "CCITT J.17" }; +static const unsigned int mpa_slots[4] = { 12, 144, 144, 0 }; +static const unsigned int mpa_samples[4] = { 384, 1152, 1152, 0 }; + + + +MPAStream::MPAStream (IBitStream & ibs, OutputStream & into): +AudioStream (ibs, into) +{ +} + +bool +MPAStream::Probe (IBitStream & bs) +{ + return bs.getbits (11) == AUDIO_SYNCWORD; +} + + +/************************************************************************* + * + * Reads initial stream parameters and displays feedback banner to users + * + *************************************************************************/ + + +void +MPAStream::Init (const int stream_num) +{ + int padding_bit; + + MuxStream::Init (AUDIO_STR_0 + stream_num, 0, // Buffer scale + muxinto.audio_buffer_size, + muxinto.vcd_zero_stuffing, + muxinto.buffers_in_audio, muxinto.always_buffers_in_audio); + mjpeg_info ("Scanning for header info: Audio stream %02x", AUDIO_STR_0 + stream_num); + + InitAUbuffer (); + + /* A.Stevens 2000 - update to be compatible up to MPEG2.5 + */ + AU_start = bs.bitcount (); + if (bs.getbits (11) == AUDIO_SYNCWORD) { + num_syncword++; + version_id = bs.getbits (2); + layer = 3 - bs.getbits (2); /* 0..2 not 1..3!! */ + protection = bs.get1bit (); + bit_rate_code = bs.getbits (4); + frequency = bs.getbits (2); + padding_bit = bs.get1bit (); + bs.get1bit (); + mode = bs.getbits (2); + mode_extension = bs.getbits (2); + copyright = bs.get1bit (); + original_copy = bs.get1bit (); + emphasis = bs.getbits (2); + + framesize = + mpa_bitrates_kbps[version_id][layer][bit_rate_code] * + mpa_slots[layer] * 1000 / mpa_freq_table[version_id][frequency]; + + size_frames[0] = framesize; + size_frames[1] = framesize + (layer == 0 ? 4 : 1); + num_frames[padding_bit]++; + access_unit.start = AU_start; + access_unit.length = size_frames[padding_bit]; + + samples_per_second = mpa_freq_table[version_id][frequency]; + + /* Presentation time-stamping */ + access_unit.PTS = static_cast < clockticks > (decoding_order) * + static_cast < clockticks > (mpa_samples[layer]) * + static_cast < clockticks > (CLOCKS) / samples_per_second; + access_unit.DTS = access_unit.PTS; + access_unit.dorder = decoding_order; + ++decoding_order; + aunits.append (access_unit); + + } else { + mjpeg_error ("Invalid MPEG Audio stream header."); + exit (1); + } + + + OutputHdrInfo (); +} + +unsigned int +MPAStream::NominalBitRate () +{ + return mpa_bitrates_kbps[version_id][layer][bit_rate_code] * 128; +} + + +unsigned int +MPAStream::SizeFrame (int rate_code, int padding) +{ + return mpa_bitrates_kbps[version_id][layer][rate_code] * + mpa_slots[layer] * 1000 / mpa_freq_table[version_id][frequency] + padding; +} + +void +MPAStream::FillAUbuffer (unsigned int frames_to_buffer) +{ + unsigned int i; + unsigned int padding_bit; + + last_buffered_AU += frames_to_buffer; + + mjpeg_debug ("Scanning %d MPEG audio frames to frame %d", frames_to_buffer, last_buffered_AU); + + while (!bs.eos () && + decoding_order < last_buffered_AU) { + + skip = access_unit.length - 4; + if (skip & 0x1) + bs.getbits (8); + if (skip & 0x2) + bs.getbits (16); + skip = skip >> 2; + + for (i = 0; i < skip; i++) { + bs.getbits (32); + } + prev_offset = AU_start; + AU_start = bs.bitcount (); + + /* Check we have reached the end of have another catenated + stream to process before finishing ... */ + if ((syncword = bs.getbits (11)) != AUDIO_SYNCWORD) { + if (!bs.eobs) { + /* There appears to be another catenated stream... */ + int next; + + mjpeg_warn ("End of component bit-stream ... seeking next"); + /* Catenated stream must start on byte boundary */ + syncword = (syncword << (8 - AU_start % 8)); + next = bs.getbits (8 - (AU_start % 8)); + AU_start = bs.bitcount () - 11; + syncword = syncword | next; + if (syncword != AUDIO_SYNCWORD) { + mjpeg_warn ("Failed to find start of next stream at %lld prev %lld !", AU_start / 8, + prev_offset / 8); + break; + } + } else + /* No catenated stream... finished! */ + break; + } + // Skip version_id:2, layer:2, protection:1 + (void) bs.getbits (5); + int rate_code = bs.getbits (4); + + // Skip frequency + (void) bs.getbits (2); + + padding_bit = bs.get1bit (); + access_unit.start = AU_start; + access_unit.length = SizeFrame (rate_code, padding_bit); + access_unit.PTS = + static_cast < clockticks > (decoding_order) * static_cast < clockticks > + (mpa_samples[layer]) * static_cast < clockticks > (CLOCKS) + / samples_per_second; + access_unit.DTS = access_unit.PTS; + access_unit.dorder = decoding_order; + decoding_order++; + aunits.append (access_unit); + num_frames[padding_bit]++; + + bs.getbits (9); + + num_syncword++; + + if (num_syncword >= old_frames + 10) { + mjpeg_debug ("Got %d frame headers.", num_syncword); + old_frames = num_syncword; + + } + + + + } + last_buffered_AU = decoding_order; + eoscan = bs.eos (); + +} + + + +void +MPAStream::Close () +{ + stream_length = AU_start >> 3; + mjpeg_info ("AUDIO_STATISTICS: %02x", stream_id); + mjpeg_info ("Audio stream length %lld bytes.", stream_length); + mjpeg_info ("Syncwords : %8u", num_syncword); + mjpeg_info ("Frames : %8u padded", num_frames[0]); + mjpeg_info ("Frames : %8u unpadded", num_frames[1]); + + bs.close (); +} + +/************************************************************************* + OutputAudioInfo + gibt gesammelte Informationen zu den Audio Access Units aus. + + Prints information on audio access units +*************************************************************************/ + +void +MPAStream::OutputHdrInfo () +{ + unsigned int bitrate; + + bitrate = mpa_bitrates_kbps[version_id][layer][bit_rate_code]; + + + mjpeg_info ("AUDIO STREAM:"); + mjpeg_info ("Audio version : %s", mpa_audio_version[version_id]); + mjpeg_info ("Layer : %8u", layer + 1); + + if (protection == 0) + mjpeg_info ("CRC checksums : yes"); + else + mjpeg_info ("CRC checksums : no"); + + if (bit_rate_code == 0) + mjpeg_info ("Bit rate : free"); + else if (bit_rate_code == 0xf) + mjpeg_info ("Bit rate : reserved"); + else + mjpeg_info ("Bit rate : %8u bytes/sec (%3u kbit/sec)", bitrate * 128, bitrate); + + if (frequency == 3) + mjpeg_info ("Frequency : reserved"); + else + mjpeg_info ("Frequency : %d Hz", mpa_freq_table[version_id][frequency]); + + mjpeg_info ("Mode : %8u %s", mode, mpa_stereo_mode[mode]); + mjpeg_info ("Mode extension : %8u", mode_extension); + mjpeg_info ("Copyright bit : %8u %s", copyright, mpa_copyright_status[copyright]); + mjpeg_info ("Original/Copy : %8u %s", original_copy, mpa_original_bit[original_copy]); + mjpeg_info ("Emphasis : %8u %s", emphasis, mpa_emphasis_mode[emphasis]); +} + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/mpegconsts.cc b/ext/mplex/mpegconsts.cc new file mode 100644 index 000000000..2265da3a9 --- /dev/null +++ b/ext/mplex/mpegconsts.cc @@ -0,0 +1,427 @@ + +/* + * mpegconsts.c: Video format constants for MPEG and utilities for display + * and conversion to format used for yuv4mpeg + * + * Copyright (C) 2001 Andrew Stevens + * Copyright (C) 2001 Matthew Marjanovic + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include "mpegconsts.h" +#include "yuv4mpeg.h" +#include "yuv4mpeg_intern.h" + +static y4m_ratio_t mpeg_framerates[] = { + Y4M_FPS_UNKNOWN, + Y4M_FPS_NTSC_FILM, + Y4M_FPS_FILM, + Y4M_FPS_PAL, + Y4M_FPS_NTSC, + Y4M_FPS_30, + Y4M_FPS_PAL_FIELD, + Y4M_FPS_NTSC_FIELD, + Y4M_FPS_60 +}; + + +#define MPEG_NUM_RATES (sizeof(mpeg_framerates)/sizeof(mpeg_framerates[0])) +const mpeg_framerate_code_t mpeg_num_framerates = MPEG_NUM_RATES; + +static const char *framerate_definitions[MPEG_NUM_RATES] = { + "illegal", + "24000.0/1001.0 (NTSC 3:2 pulldown converted FILM)", + "24.0 (NATIVE FILM)", + "25.0 (PAL/SECAM VIDEO / converted FILM)", + "30000.0/1001.0 (NTSC VIDEO)", + "30.0", + "50.0 (PAL FIELD RATE)", + "60000.0/1001.0 (NTSC FIELD RATE)", + "60.0" +}; + + +static const char *mpeg1_aspect_ratio_definitions[] = { + "1:1 (square pixels)", + "1:0.6735", + "1:0.7031 (16:9 Anamorphic PAL/SECAM for 720x578/352x288 images)", + "1:0.7615", + "1:0.8055", + "1:0.8437 (16:9 Anamorphic NTSC for 720x480/352x240 images)", + "1:0.8935", + "1:0.9375 (4:3 PAL/SECAM for 720x578/352x288 images)", + "1:0.9815", + "1:1.0255", + "1:1:0695", + "1:1.1250 (4:3 NTSC for 720x480/352x240 images)", + "1:1.1575", + "1:1.2015" +}; + +static const y4m_ratio_t mpeg1_aspect_ratios[] = { + Y4M_SAR_MPEG1_1, + Y4M_SAR_MPEG1_2, + Y4M_SAR_MPEG1_3, /* Anamorphic 16:9 PAL */ + Y4M_SAR_MPEG1_4, + Y4M_SAR_MPEG1_5, + Y4M_SAR_MPEG1_6, /* Anamorphic 16:9 NTSC */ + Y4M_SAR_MPEG1_7, + Y4M_SAR_MPEG1_8, /* PAL/SECAM 4:3 */ + Y4M_SAR_MPEG1_9, + Y4M_SAR_MPEG1_10, + Y4M_SAR_MPEG1_11, + Y4M_SAR_MPEG1_12, /* NTSC 4:3 */ + Y4M_SAR_MPEG1_13, + Y4M_SAR_MPEG1_14, +}; + +static const char *mpeg2_aspect_ratio_definitions[] = { + "1:1 pixels", + "4:3 display", + "16:9 display", + "2.21:1 display" +}; + + +static const y4m_ratio_t mpeg2_aspect_ratios[] = { + Y4M_DAR_MPEG2_1, + Y4M_DAR_MPEG2_2, + Y4M_DAR_MPEG2_3, + Y4M_DAR_MPEG2_4 +}; + +static const char **aspect_ratio_definitions[2] = { + mpeg1_aspect_ratio_definitions, + mpeg2_aspect_ratio_definitions +}; + +static const y4m_ratio_t *mpeg_aspect_ratios[2] = { + mpeg1_aspect_ratios, + mpeg2_aspect_ratios +}; + +const mpeg_aspect_code_t mpeg_num_aspect_ratios[2] = { + sizeof (mpeg1_aspect_ratios) / sizeof (mpeg1_aspect_ratios[0]), + sizeof (mpeg2_aspect_ratios) / sizeof (mpeg2_aspect_ratios[0]) +}; + +/* + * Convert MPEG frame-rate code to corresponding frame-rate + */ + +y4m_ratio_t +mpeg_framerate (mpeg_framerate_code_t code) +{ + if (code == 0 || code > mpeg_num_framerates) + return y4m_fps_UNKNOWN; + else + return mpeg_framerates[code]; +} + +/* + * Look-up MPEG frame rate code for a (exact) frame rate. + */ + + +mpeg_framerate_code_t +mpeg_framerate_code (y4m_ratio_t framerate) +{ + mpeg_framerate_code_t i; + + y4m_ratio_reduce (&framerate); + for (i = 1; i < mpeg_num_framerates; ++i) { + if (Y4M_RATIO_EQL (framerate, mpeg_framerates[i])) + return i; + } + return 0; +} + + +/* small enough to distinguish 1/1000 from 1/1001 */ +#define MPEG_FPS_TOLERANCE 0.0001 + + +y4m_ratio_t +mpeg_conform_framerate (double fps) +{ + mpeg_framerate_code_t i; + y4m_ratio_t result; + + /* try to match it to a standard frame rate */ + for (i = 1; i < mpeg_num_framerates; i++) { + double deviation = 1.0 - (Y4M_RATIO_DBL (mpeg_framerates[i]) / fps); + + if ((deviation > -MPEG_FPS_TOLERANCE) && (deviation < +MPEG_FPS_TOLERANCE)) + return mpeg_framerates[i]; + } + /* no luck? just turn it into a ratio (6 decimal place accuracy) */ + result.n = (int) ((fps * 1000000.0) + 0.5); + result.d = 1000000; + y4m_ratio_reduce (&result); + return result; +} + + + +/* + * Convert MPEG aspect-ratio code to corresponding aspect-ratio + */ + +y4m_ratio_t +mpeg_aspect_ratio (int mpeg_version, mpeg_aspect_code_t code) +{ + y4m_ratio_t ratio; + + if (mpeg_version < 1 || mpeg_version > 2) + return y4m_sar_UNKNOWN; + if (code == 0 || code > mpeg_num_aspect_ratios[mpeg_version - 1]) + return y4m_sar_UNKNOWN; + else { + ratio = mpeg_aspect_ratios[mpeg_version - 1][code - 1]; + y4m_ratio_reduce (&ratio); + return ratio; + } +} + +/* + * Look-up corresponding MPEG aspect ratio code given an exact aspect ratio. + * + * WARNING: The semantics of aspect ratio coding *changed* between + * MPEG1 and MPEG2. In MPEG1 it is the *pixel* aspect ratio. In + * MPEG2 it is the (far more sensible) aspect ratio of the eventual + * display. + * + */ + +mpeg_aspect_code_t +mpeg_frame_aspect_code (int mpeg_version, y4m_ratio_t aspect_ratio) +{ + mpeg_aspect_code_t i; + y4m_ratio_t red_ratio = aspect_ratio; + + y4m_ratio_reduce (&red_ratio); + if (mpeg_version < 1 || mpeg_version > 2) + return 0; + for (i = 1; i < mpeg_num_aspect_ratios[mpeg_version - 1]; ++i) { + y4m_ratio_t red_entry = mpeg_aspect_ratios[mpeg_version - 1][i - 1]; + + y4m_ratio_reduce (&red_entry); + if (Y4M_RATIO_EQL (red_entry, red_ratio)) + return i; + } + + return 0; + +} + + + +/* + * Guess the correct MPEG aspect ratio code, + * given the true sample aspect ratio and frame size of a video stream + * (and the MPEG version, 1 or 2). + * + * Returns 0 if it has no good guess. + * + */ + + +/* this is big enough to accommodate the difference between 720 and 704 */ +#define GUESS_ASPECT_TOLERANCE 0.03 + +mpeg_aspect_code_t +mpeg_guess_mpeg_aspect_code (int mpeg_version, y4m_ratio_t sampleaspect, + int frame_width, int frame_height) +{ + if (Y4M_RATIO_EQL (sampleaspect, y4m_sar_UNKNOWN)) { + return 0; + } + switch (mpeg_version) { + case 1: + if (Y4M_RATIO_EQL (sampleaspect, y4m_sar_SQUARE)) { + return 1; + } else if (Y4M_RATIO_EQL (sampleaspect, y4m_sar_NTSC_CCIR601)) { + return 12; + } else if (Y4M_RATIO_EQL (sampleaspect, y4m_sar_NTSC_16_9)) { + return 6; + } else if (Y4M_RATIO_EQL (sampleaspect, y4m_sar_PAL_CCIR601)) { + return 8; + } else if (Y4M_RATIO_EQL (sampleaspect, y4m_sar_PAL_16_9)) { + return 3; + } + return 0; + break; + case 2: + if (Y4M_RATIO_EQL (sampleaspect, y4m_sar_SQUARE)) { + return 1; /* '1' means square *pixels* in MPEG-2; go figure. */ + } else { + unsigned int i; + double true_far; /* true frame aspect ratio */ + + true_far = + (double) (sampleaspect.n * frame_width) / (double) (sampleaspect.d * frame_height); + /* start at '2'... */ + for (i = 2; i < mpeg_num_aspect_ratios[mpeg_version - 1]; i++) { + double ratio = true_far / Y4M_RATIO_DBL (mpeg_aspect_ratios[mpeg_version - 1][i - 1]); + + if ((ratio > (1.0 - GUESS_ASPECT_TOLERANCE)) && (ratio < (1.0 + GUESS_ASPECT_TOLERANCE))) + return i; + } + return 0; + } + break; + default: + return 0; + break; + } +} + + + + +/* + * Guess the true sample aspect ratio of a video stream, + * given the MPEG aspect ratio code and the actual frame size + * (and the MPEG version, 1 or 2). + * + * Returns y4m_sar_UNKNOWN if it has no good guess. + * + */ +y4m_ratio_t +mpeg_guess_sample_aspect_ratio (int mpeg_version, + mpeg_aspect_code_t code, int frame_width, int frame_height) +{ + switch (mpeg_version) { + case 1: + /* MPEG-1 codes turn into SAR's, just not quite the right ones. + For the common/known values, we provide the ratio used in practice, + otherwise say we don't know. */ + switch (code) { + case 1: + return y4m_sar_SQUARE; + break; + case 3: + return y4m_sar_PAL_16_9; + break; + case 6: + return y4m_sar_NTSC_16_9; + break; + case 8: + return y4m_sar_PAL_CCIR601; + break; + case 12: + return y4m_sar_NTSC_CCIR601; + break; + default: + return y4m_sar_UNKNOWN; + break; + } + break; + case 2: + /* MPEG-2 codes turn into Frame Aspect Ratios, though not exactly the + FAR's used in practice. For common/standard frame sizes, we provide + the original SAR; otherwise, we say we don't know. */ + if (code == 1) { + return y4m_sar_SQUARE; /* '1' means square *pixels* in MPEG-2 */ + } else if ((code >= 2) && (code <= 4)) { + return y4m_guess_sar (frame_width, frame_height, mpeg2_aspect_ratios[code - 1]); + } else { + return y4m_sar_UNKNOWN; + } + break; + default: + return y4m_sar_UNKNOWN; + break; + } +} + + + + + +/* + * Look-up MPEG explanatory definition string for frame rate code + * + */ + + +const char * +mpeg_framerate_code_definition (mpeg_framerate_code_t code) +{ + if (code == 0 || code >= mpeg_num_framerates) + return "UNDEFINED: illegal/reserved frame-rate ratio code"; + + return framerate_definitions[code]; +} + +/* + * Look-up MPEG explanatory definition string aspect ratio code for an + * aspect ratio code + * + */ + +const char * +mpeg_aspect_code_definition (int mpeg_version, mpeg_aspect_code_t code) +{ + if (mpeg_version < 1 || mpeg_version > 2) + return "UNDEFINED: illegal MPEG version"; + + if (code < 1 || code > mpeg_num_aspect_ratios[mpeg_version - 1]) + return "UNDEFINED: illegal aspect ratio code"; + + return aspect_ratio_definitions[mpeg_version - 1][code - 1]; +} + + +/* + * Look-up explanatory definition of interlace field order code + * + */ + +const char * +mpeg_interlace_code_definition (int yuv4m_interlace_code) +{ + const char *def; + + switch (yuv4m_interlace_code) { + case Y4M_UNKNOWN: + def = "unknown"; + break; + case Y4M_ILACE_NONE: + def = "none/progressive"; + break; + case Y4M_ILACE_TOP_FIRST: + def = "top-field-first"; + break; + case Y4M_ILACE_BOTTOM_FIRST: + def = "bottom-field-first"; + break; + default: + def = "UNDEFINED: illegal video interlacing type-code!"; + break; + } + return def; +} + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/mpegconsts.h b/ext/mplex/mpegconsts.h new file mode 100644 index 000000000..461fd2dbd --- /dev/null +++ b/ext/mplex/mpegconsts.h @@ -0,0 +1,149 @@ + +/* + * mpegconsts.c: Video format constants for MPEG and utilities for display + * and conversion to format used for yuv4mpeg + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __MPEGCONSTS_H__ +#define __MPEGCONSTS_H__ + + +#include "yuv4mpeg.h" + + +typedef unsigned int mpeg_framerate_code_t; +typedef unsigned int mpeg_aspect_code_t; + +extern const mpeg_framerate_code_t mpeg_num_framerates; +extern const mpeg_aspect_code_t mpeg_num_aspect_ratios[2]; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Convert MPEG frame-rate code to corresponding frame-rate + * y4m_fps_UNKNOWN = { 0, 0 } = Undefined/resrerved code. + */ + +y4m_ratio_t +mpeg_framerate( mpeg_framerate_code_t code ); + + +/* + * Look-up MPEG frame rate code for a (exact) frame rate. + * 0 = No MPEG code defined for frame-rate + */ + +mpeg_framerate_code_t +mpeg_framerate_code( y4m_ratio_t framerate ); + + +/* + * Convert floating-point framerate to an exact ratio. + * Uses a standard MPEG rate, if it finds one within MPEG_FPS_TOLERANCE + * (see mpegconsts.c), otherwise uses "fps:1000000" as the ratio. + */ + +y4m_ratio_t +mpeg_conform_framerate( double fps ); + + +/* + * Convert MPEG aspect ratio code to corresponding aspect ratio + * + * WARNING: The semantics of aspect ratio coding *changed* between + * MPEG1 and MPEG2. In MPEG1 it is the *pixel* aspect ratio. In + * MPEG2 it is the (far more sensible) aspect ratio of the eventual + * display. + * + */ + +y4m_ratio_t +mpeg_aspect_ratio( int mpeg_version, mpeg_aspect_code_t code ); + +/* + * Look-up MPEG aspect ratio code for an aspect ratio - tolerance + * is Y4M_ASPECT_MULT used by YUV4MPEG (see yuv4mpeg_intern.h) + * + * WARNING: The semantics of aspect ratio coding *changed* between + * MPEG1 and MPEG2. In MPEG1 it is the *pixel* aspect ratio. In + * MPEG2 it is the (far more sensible) aspect ratio of the eventual + * display. + * + */ + +mpeg_aspect_code_t +mpeg_frame_aspect_code( int mpeg_version, y4m_ratio_t aspect_ratio ); + +/* + * Look-up MPEG explanatory definition string aspect ratio code for an + * aspect ratio code + * + */ + +const char * +mpeg_aspect_code_definition( int mpeg_version, mpeg_aspect_code_t code ); + +/* + * Look-up MPEG explanatory definition string aspect ratio code for an + * frame rate code + * + */ + +const char * +mpeg_framerate_code_definition( mpeg_framerate_code_t code ); + +const char * +mpeg_interlace_code_definition( int yuv4m_interlace_code ); + + +/* + * Guess the correct MPEG aspect ratio code, + * given the true sample aspect ratio and frame size of a video stream + * (and the MPEG version, 1 or 2). + * + * Returns 0 if it has no good answer. + * + */ +mpeg_aspect_code_t +mpeg_guess_mpeg_aspect_code(int mpeg_version, y4m_ratio_t sampleaspect, + int frame_width, int frame_height); + +/* + * Guess the true sample aspect ratio of a video stream, + * given the MPEG aspect ratio code and the actual frame size + * (and the MPEG version, 1 or 2). + * + * Returns y4m_sar_UNKNOWN if it has no good answer. + * + */ +y4m_ratio_t +mpeg_guess_sample_aspect_ratio(int mpeg_version, + mpeg_aspect_code_t code, + int frame_width, int frame_height); + + +#ifdef __cplusplus +}; +#endif + + + +#endif /* __MPEGCONSTS_H__ */ diff --git a/ext/mplex/mplexconsts.hh b/ext/mplex/mplexconsts.hh new file mode 100644 index 000000000..c3fe6ce1f --- /dev/null +++ b/ext/mplex/mplexconsts.hh @@ -0,0 +1,83 @@ +#ifndef __MPLEXCONSTS_H__ +#define __MPLEXCONSTS_H__ + + +#define SEQUENCE_HEADER 0x000001b3 +#define SEQUENCE_END 0x000001b7 +#define PICTURE_START 0x00000100 +#define EXT_START_CODE 0x000001b5 +#define GROUP_START 0x000001b8 +#define SYNCWORD_START 0x000001 + +#define IFRAME 1 +#define PFRAME 2 +#define BFRAME 3 +#define DFRAME 4 +#define NOFRAME 5 + +#define PIC_TOP_FIELD 1 +#define PIC_BOT_FIELD 2 +#define PIC_FRAME 3 + +#define CODING_EXT_ID 8 +#define AUDIO_SYNCWORD 0x7ff + + +#define PACK_START 0x000001ba +#define SYS_HEADER_START 0x000001bb +#define ISO11172_END 0x000001b9 +#define PACKET_START 0x000001 + +#define MAX_FFFFFFFF 4294967295.0 /* = 0xffffffff in dec. */ + +#define CLOCKS_per_90Kth_sec 300 + +#define CLOCKS (CLOCKS_per_90Kth_sec*90000) +/* MPEG-2 System Clock Hertz - we divide down by 300.0 for MPEG-1*/ + +/* Range of sizes of the fields following the packet length field in packet header: + used to calculate if recieve buffers will have enough space... */ + +#define MPEG2_BUFFERINFO_LENGTH 3 +#define MPEG1_BUFFERINFO_LENGTH 2 +#define DTS_PTS_TIMESTAMP_LENGTH 5 +#define MPEG2_AFTER_PACKET_LENGTH_MIN 3 +#define MPEG1_AFTER_PACKET_LENGTH_MIN (0+1) + + /* Sector under-size below which header stuffing rather than padding packets + or post-packet zero stuffing is used. *Must* be less than 20 for VCD + multiplexing to work correctly! + */ + +#define MINIMUM_PADDING_PACKET_SIZE 10 + +#define PACKET_HEADER_SIZE 6 + +#define AUDIO_STREAMS 0xb8 /* Marker Audio Streams */ +#define VIDEO_STREAMS 0xb9 /* Marker Video Streams */ +#define AUDIO_STR_0 0xc0 /* Marker Audio Stream0 */ +#define VIDEO_STR_0 0xe0 /* Marker Video Stream0 */ +#define PADDING_STR 0xbe /* Marker Padding Stream */ +#define PRIVATE_STR_1 0xbd /* private stream 1 */ +#define PRIVATE_STR_2 0xbf /* private stream 2 */ +#define AC3_SUB_STR_0 0x80 /* AC3 substream id 0 */ + +#define LPCM_SUB_STR_0 0xa0 /* LPCM substream id 0 */ + +#define ZERO_STUFFING_BYTE 0 +#define STUFFING_BYTE 0xff +#define RESERVED_BYTE 0xff +#define TIMESTAMPBITS_NO 0 /* Flag NO timestamps */ +#define TIMESTAMPBITS_PTS 2 /* Flag PTS timestamp */ +#define TIMESTAMPBITS_DTS 1 /* Flag PTS timestamp */ +#define TIMESTAMPBITS_PTS_DTS (TIMESTAMPBITS_DTS|TIMESTAMPBITS_PTS) /* Flag BOTH timestamps */ + +#define MARKER_MPEG1_SCR 2 /* Marker SCR */ +#define MARKER_MPEG2_SCR 1 /* These don't need to be distinct! */ +#define MARKER_JUST_PTS 2 /* Marker only PTS */ +#define MARKER_PTS 3 /* Marker PTS */ +#define MARKER_DTS 1 /* Marker DTS */ +#define MARKER_NO_TIMESTAMPS 0x0f /* Marker NO timestamps */ + + +#endif // __MPLEXCONSTS_H__ diff --git a/ext/mplex/multplex.cc b/ext/mplex/multplex.cc new file mode 100644 index 000000000..5a1d4b1bd --- /dev/null +++ b/ext/mplex/multplex.cc @@ -0,0 +1,1120 @@ + +#include +#include +#include +#include + +#include +#include +#include + +#include "videostrm.hh" +#include "outputstream.hh" + + +/******************************************************************* + Find the timecode corresponding to given position in the system stream + (assuming the SCR starts at 0 at the beginning of the stream +@param bytepos byte position in the stream +@param ts returns the number of clockticks the bytepos is from the file start +****************************************************************** */ + +void +OutputStream::ByteposTimecode (bitcount_t bytepos, clockticks & ts) +{ + ts = (bytepos * CLOCKS) / static_cast < bitcount_t > (dmux_rate); +} + + +/********** + * + * UpdateSectorHeaders - Update the sector headers after a change in stream + * position / SCR + ********** + + ********** + * + * NextPosAndSCR - Update nominal (may be >= actual) byte count + * and SCR to next output sector. + * + ********/ + +void +OutputStream::NextPosAndSCR () +{ + bytes_output += sector_transport_size; + ByteposTimecode (bytes_output, current_SCR); + if (start_of_new_pack) { + psstrm->CreatePack (&pack_header, current_SCR, mux_rate); + pack_header_ptr = &pack_header; + if (include_sys_header) + sys_header_ptr = &sys_header; + else + sys_header_ptr = NULL; + + } else + pack_header_ptr = NULL; +} + + +/********** + * + * NextPosAndSCR - Update nominal (may be >= actual) byte count + * and SCR to next output sector. + * @param bytepos byte position in the stream + ********/ + +void +OutputStream::SetPosAndSCR (bitcount_t bytepos) +{ + bytes_output = bytepos; + ByteposTimecode (bytes_output, current_SCR); + if (start_of_new_pack) { + psstrm->CreatePack (&pack_header, current_SCR, mux_rate); + pack_header_ptr = &pack_header; + if (include_sys_header) + sys_header_ptr = &sys_header; + else + sys_header_ptr = NULL; + + } else + pack_header_ptr = NULL; +} + +/* + Stream syntax parameters. +*/ + + +/****************************************************************** + + Initialisation of stream syntax paramters based on selected + user options. +******************************************************************/ + + +// TODO: this mixes class member parameters with opt_ globals... + +void +OutputStream::InitSyntaxParameters () +{ + video_buffer_size = 0; + seg_starts_with_video = false; + audio_buffer_size = 4 * 1024; + + switch (opt_mux_format) { + case MPEG_FORMAT_VCD: + opt_data_rate = 75 * 2352; /* 75 raw CD sectors/sec */ + video_buffer_size = 46 * 1024; + opt_VBR = 0; + + case MPEG_FORMAT_VCD_NSR: /* VCD format, non-standard rate */ + mjpeg_info ("Selecting VCD output profile"); + if (video_buffer_size == 0) + video_buffer_size = opt_buffer_size * 1024; + vbr = opt_VBR; + opt_mpeg = 1; + packets_per_pack = 1; + sys_header_in_pack1 = 0; + always_sys_header_in_pack = 0; + sector_transport_size = 2352; /* Each 2352 bytes with 2324 bytes payload */ + transport_prefix_sectors = 30; + sector_size = 2324; + buffers_in_video = 1; + always_buffers_in_video = 0; + buffers_in_audio = 1; // This is needed as otherwise we have + always_buffers_in_audio = 1; // to stuff the packer header which + // must be 13 bytes for VCD audio + vcd_zero_stuffing = 20; // The famous 20 zero bytes for VCD + // audio sectors. + dtspts_for_all_vau = false; + sector_align_iframeAUs = false; + timestamp_iframe_only = false; + seg_starts_with_video = true; + break; + + case MPEG_FORMAT_MPEG2: + mjpeg_info ("Selecting generic MPEG2 output profile"); + opt_mpeg = 2; + packets_per_pack = 1; + sys_header_in_pack1 = 1; + always_sys_header_in_pack = 0; + sector_transport_size = 2048; /* Each 2352 bytes with 2324 bytes payload */ + transport_prefix_sectors = 0; + sector_size = 2048; + video_buffer_size = 234 * 1024; + buffers_in_video = 1; + always_buffers_in_video = 0; + buffers_in_audio = 1; + always_buffers_in_audio = 1; + vcd_zero_stuffing = 0; + dtspts_for_all_vau = 0; + sector_align_iframeAUs = false; + timestamp_iframe_only = false; + video_buffers_iframe_only = false; + vbr = opt_VBR; + break; + + case MPEG_FORMAT_SVCD: + opt_data_rate = 150 * 2324; + video_buffer_size = 230 * 1024; + + case MPEG_FORMAT_SVCD_NSR: /* Non-standard data-rate */ + mjpeg_info ("Selecting SVCD output profile"); + if (video_buffer_size == 0) + video_buffer_size = opt_buffer_size * 1024; + opt_mpeg = 2; + packets_per_pack = 1; + sys_header_in_pack1 = 0; + always_sys_header_in_pack = 0; + sector_transport_size = 2324; + transport_prefix_sectors = 0; + sector_size = 2324; + vbr = true; + buffers_in_video = 1; + always_buffers_in_video = 0; + buffers_in_audio = 1; + always_buffers_in_audio = 0; + vcd_zero_stuffing = 0; + dtspts_for_all_vau = 0; + sector_align_iframeAUs = true; + seg_starts_with_video = true; + timestamp_iframe_only = false; + video_buffers_iframe_only = false; + break; + + case MPEG_FORMAT_VCD_STILL: + opt_data_rate = 75 * 2352; /* 75 raw CD sectors/sec */ + vbr = false; + opt_mpeg = 1; + packets_per_pack = 1; + sys_header_in_pack1 = 0; + always_sys_header_in_pack = 0; + sector_transport_size = 2352; /* Each 2352 bytes with 2324 bytes payload */ + transport_prefix_sectors = 0; + sector_size = 2324; + buffers_in_video = 1; + always_buffers_in_video = 0; + buffers_in_audio = 1; + always_buffers_in_audio = 0; + vcd_zero_stuffing = 20; + dtspts_for_all_vau = 1; + sector_align_iframeAUs = true; + timestamp_iframe_only = false; + video_buffers_iframe_only = false; + if (opt_buffer_size == 0) + opt_buffer_size = 46; + else if (opt_buffer_size > 220) { + mjpeg_error_exit1 ("VCD stills has max. permissible video buffer size of 220KB"); + } else { + /* Add a margin for sequence header overheads for HR stills */ + /* So the user simply specifies the nominal size... */ + opt_buffer_size += 4; + } + video_buffer_size = opt_buffer_size * 1024; + break; + + case MPEG_FORMAT_SVCD_STILL: + mjpeg_info ("Selecting SVCD output profile"); + if (opt_data_rate == 0) + opt_data_rate = 150 * 2324; + video_buffer_size = 230 * 1024; + opt_mpeg = 2; + packets_per_pack = 1; + sys_header_in_pack1 = 0; + always_sys_header_in_pack = 0; + sector_transport_size = 2324; + transport_prefix_sectors = 0; + sector_size = 2324; + vbr = true; + buffers_in_video = 1; + always_buffers_in_video = 0; + buffers_in_audio = 1; + always_buffers_in_audio = 0; + vcd_zero_stuffing = 0; + dtspts_for_all_vau = 0; + sector_align_iframeAUs = true; + timestamp_iframe_only = false; + video_buffers_iframe_only = false; + break; + + case MPEG_FORMAT_DVD: + mjpeg_info ("Selecting DVD output profile (INCOMPLETE!!!!)"); + opt_data_rate = 1260000; + opt_mpeg = 2; + packets_per_pack = 1; + sys_header_in_pack1 = false; // Handle by control packets + always_sys_header_in_pack = false; + sector_transport_size = 2048; + transport_prefix_sectors = 0; + sector_size = 2048; + video_buffer_size = 232 * 1024; + buffers_in_video = true; + always_buffers_in_video = false; + buffers_in_audio = true; + always_buffers_in_audio = false; + vcd_zero_stuffing = 0; + dtspts_for_all_vau = 0; + sector_align_iframeAUs = true; + timestamp_iframe_only = true; + video_buffers_iframe_only = true; + vbr = true; + if (opt_max_segment_size == 0) + opt_max_segment_size = 2000 * 1024 * 1024; + break; + + default: /* MPEG_FORMAT_MPEG1 - auto format MPEG1 */ + mjpeg_info ("Selecting generic MPEG1 output profile"); + opt_mpeg = 1; + vbr = opt_VBR; + packets_per_pack = opt_packets_per_pack; + always_sys_header_in_pack = opt_always_system_headers; + sys_header_in_pack1 = 1; + sector_transport_size = opt_sector_size; + transport_prefix_sectors = 0; + sector_size = opt_sector_size; + if (opt_buffer_size == 0) { + opt_buffer_size = 46; + } + video_buffer_size = opt_buffer_size * 1024; + buffers_in_video = 1; + always_buffers_in_video = 1; + buffers_in_audio = 0; + always_buffers_in_audio = 1; + vcd_zero_stuffing = 0; + dtspts_for_all_vau = 0; + sector_align_iframeAUs = false; + timestamp_iframe_only = false; + video_buffers_iframe_only = false; + break; + } + +} + +/** + * Compute the number of run-in sectors needed to fill up the buffers to + * suit the type of stream being muxed. + * + * For stills we have to ensure an entire buffer is loaded as we only + * ever process one frame at a time. + * @returns the number of run-in sectors needed to fill up the buffers to suit the type of stream being muxed. + */ + +unsigned int +OutputStream::RunInSectors () +{ + vector < ElementaryStream * >::iterator str; + unsigned int sectors_delay = 1; + + for (str = vstreams.begin (); str < vstreams.end (); ++str) { + + if (MPEG_STILLS_FORMAT (opt_mux_format)) { + sectors_delay += (unsigned int) (1.02 * (*str)->BufferSize ()) / sector_size + 2; + } else if (vbr) + sectors_delay += 3 * (*str)->BufferSize () / (4 * sector_size); + else + sectors_delay += 5 * (*str)->BufferSize () / (6 * sector_size); + } + sectors_delay += astreams.size (); + return sectors_delay; +} + +/** + Initializes the output stream. Traverses the input files and calculates their payloads. + Estimates the multiplex rate. Estimates the neccessary stream delay for the different substreams. + */ + + +void +OutputStream::Init (vector < ElementaryStream * >*strms, PS_Stream *strm) +{ + vector < ElementaryStream * >::iterator str; + clockticks delay; + unsigned int sectors_delay; + + Pack_struc dummy_pack; + Sys_header_struc dummy_sys_header; + Sys_header_struc *sys_hdr; + unsigned int nominal_rate_sum; + + packets_left_in_pack = 0; /* Suppress warning */ + video_first = false; + + estreams = strms; + + for (str = estreams->begin (); str < estreams->end (); ++str) { + switch ((*str)->Kind ()) { + case ElementaryStream::audio: + astreams.push_back (*str); + break; + case ElementaryStream::video: + vstreams.push_back (*str); + break; + default: + break; + } + + completed.push_back (false); + } + + mjpeg_info ("SYSTEMS/PROGRAM stream:"); + this->psstrm = strm; + + psstrm->Init (opt_mpeg, sector_size, opt_max_segment_size); + + /* These are used to make (conservative) decisions + about whether a packet should fit into the recieve buffers... + Audio packets always have PTS fields, video packets needn'. + TODO: Really this should be encapsulated in Elementary stream...? + */ + psstrm->CreatePack (&dummy_pack, 0, mux_rate); + if (always_sys_header_in_pack) { + vector < MuxStream * >muxstreams; + AppendMuxStreamsOf (*estreams, muxstreams); + psstrm->CreateSysHeader (&dummy_sys_header, mux_rate, !vbr, 1, true, true, muxstreams); + sys_hdr = &dummy_sys_header; + } else + sys_hdr = NULL; + + nominal_rate_sum = 0; + for (str = estreams->begin (); str < estreams->end (); ++str) { + switch ((*str)->Kind ()) { + case ElementaryStream::audio: + (*str)->SetMaxPacketData (psstrm->PacketPayload (**str, NULL, NULL, + false, true, false)); + (*str)->SetMinPacketData (psstrm->PacketPayload (**str, sys_hdr, &dummy_pack, + always_buffers_in_audio, true, false)); + + break; + case ElementaryStream::video: + (*str)->SetMaxPacketData (psstrm->PacketPayload (**str, NULL, NULL, + false, false, false)); + (*str)->SetMinPacketData (psstrm->PacketPayload (**str, sys_hdr, &dummy_pack, + always_buffers_in_video, true, true)); + break; + default: + mjpeg_error_exit1 ("INTERNAL: Only audio and video payload calculations implemented!"); + + } + + if ((*str)->NominalBitRate () == 0 && opt_data_rate == 0) + mjpeg_error_exit1 + ("Variable bit-rate stream present: output stream (max) data-rate *must* be specified!"); + nominal_rate_sum += (*str)->NominalBitRate (); + + } + + /* Attempt to guess a sensible mux rate for the given video and * + audio estreams-> This is a rough and ready guess for MPEG-1 like + formats. */ + + + dmux_rate = static_cast < int >(1.015 * nominal_rate_sum); + + dmux_rate = (dmux_rate / 50 + 25) * 50; + + mjpeg_info ("rough-guess multiplexed stream data rate : %07d", dmux_rate * 8); + if (opt_data_rate != 0) + mjpeg_info ("target data-rate specified : %7d", opt_data_rate * 8); + + if (opt_data_rate == 0) { + mjpeg_info ("Setting best-guess data rate."); + } else if (opt_data_rate >= dmux_rate) { + mjpeg_info ("Setting specified specified data rate: %7d", opt_data_rate * 8); + dmux_rate = opt_data_rate; + } else if (opt_data_rate < dmux_rate) { + mjpeg_warn ("Target data rate lower than computed requirement!"); + mjpeg_warn ("N.b. a 20%% or so discrepancy in variable bit-rate"); + mjpeg_warn ("streams is common and harmless provided no time-outs will occur"); + dmux_rate = opt_data_rate; + } + + mux_rate = dmux_rate / 50; + + /* To avoid Buffer underflow, the DTS of the first video and audio AU's + must be offset sufficiently forward of the SCR to allow the buffer + time to fill before decoding starts. Calculate the necessary delays... + */ + + sectors_delay = RunInSectors (); + + ByteposTimecode (static_cast < bitcount_t > (sectors_delay * sector_transport_size), delay); + + + video_delay = delay + static_cast < clockticks > (opt_video_offset * CLOCKS / 1000); + audio_delay = delay + static_cast < clockticks > (opt_audio_offset * CLOCKS / 1000); + mjpeg_info ("Sectors = %d Video delay = %lld Audio delay = %lld", + sectors_delay, video_delay / 300, audio_delay / 300); + + + // + // Now that all mux parameters are set we can trigger parsing + // of actual input stream data and calculation of associated + // PTS/DTS by causing the read of the first AU's... + // + for (str = estreams->begin (); str < estreams->end (); ++str) { + (*str)->NextAU (); + } + + + // + // Now that we have both output and input streams initialised and + // data-rates set we can make a decent job of setting the maximum + // STD buffer delay in video streams. + // + + for (str = vstreams.begin (); str < vstreams.end (); ++str) { + static_cast < VideoStream * >(*str)->SetMaxStdBufferDelay (dmux_rate); + } + + + + /* Let's try to read in unit after unit and to write it out into + the outputstream. The only difficulty herein lies into the + buffer management, and into the fact the the actual access + unit *has* to arrive in time, that means the whole unit + (better yet, packet data), has to arrive before arrival of + DTS. If both buffers are full we'll generate a padding packet + + Of course, when we start we're starting a new segment with no + bytes output... + */ + + ByteposTimecode (sector_transport_size, ticks_per_sector); + seg_state = start_segment; + running_out = false; +} + +/** + Prints the current status of the substreams. + @param level the desired log level + */ +void +OutputStream::MuxStatus (log_level_t level) +{ + vector < ElementaryStream * >::iterator str; + for (str = estreams->begin (); str < estreams->end (); ++str) { + switch ((*str)->Kind ()) { + case ElementaryStream::video: + mjpeg_log (level, + "Video %02x: buf=%7d frame=%06d sector=%08d", + (*str)->stream_id, (*str)->bufmodel.Space (), (*str)->au->dorder, (*str)->nsec); + break; + case ElementaryStream::audio: + mjpeg_log (level, + "Audio %02x: buf=%7d frame=%06d sector=%08d", + (*str)->stream_id, (*str)->bufmodel.Space (), (*str)->au->dorder, (*str)->nsec); + break; + default: + mjpeg_log (level, + "Other %02x: buf=%7d sector=%08d", + (*str)->stream_id, (*str)->bufmodel.Space (), (*str)->nsec); + break; + } + } + if (!vbr) + mjpeg_log (level, "Padding : sector=%08d", pstrm.nsec); + + +} + + +/** + Append input substreams to the output multiplex stream. + */ +void +OutputStream::AppendMuxStreamsOf (vector < ElementaryStream * >&elem, vector < MuxStream * >&mux) +{ + vector < ElementaryStream * >::iterator str; + for (str = elem.begin (); str < elem.end (); ++str) { + mux.push_back (static_cast < MuxStream * >(*str)); + } +} + +/****************************************************************** + Program start-up packets. Generate any irregular packets +needed at the start of the stream... + Note: *must* leave a sensible in-stream system header in + sys_header. + TODO: get rid of this grotty sys_header global. +******************************************************************/ +void +OutputStream::OutputPrefix () +{ + vector < MuxStream * >vmux, amux, emux; + AppendMuxStreamsOf (vstreams, vmux); + AppendMuxStreamsOf (astreams, amux); + AppendMuxStreamsOf (*estreams, emux); + + /* Deal with transport padding */ + SetPosAndSCR (bytes_output + transport_prefix_sectors * sector_transport_size); + + /* VCD: Two padding packets with video and audio system headers */ + split_at_seq_end = !opt_multifile_segment; + + switch (opt_mux_format) { + case MPEG_FORMAT_VCD: + case MPEG_FORMAT_VCD_NSR: + + /* Annoyingly VCD generates seperate system headers for + audio and video ... DOH... */ + if (astreams.size () > 1 || vstreams.size () > 1 || + astreams.size () + vstreams.size () != estreams->size ()) { + mjpeg_error_exit1 ("VCD man only have max. 1 audio and 1 video stream"); + } + /* First packet carries video-info-only sys_header */ + psstrm->CreateSysHeader (&sys_header, mux_rate, false, true, true, true, vmux); + sys_header_ptr = &sys_header; + pack_header_ptr = &pack_header; + OutputPadding (false); + + /* Second packet carries audio-info-only sys_header */ + psstrm->CreateSysHeader (&sys_header, mux_rate, false, true, true, true, amux); + sys_header_ptr = &sys_header; + pack_header_ptr = &pack_header; + OutputPadding (true); + break; + + case MPEG_FORMAT_SVCD: + case MPEG_FORMAT_SVCD_NSR: + /* First packet carries sys_header */ + psstrm->CreateSysHeader (&sys_header, mux_rate, !vbr, true, true, true, emux); + sys_header_ptr = &sys_header; + pack_header_ptr = &pack_header; + OutputPadding (false); + break; + + case MPEG_FORMAT_VCD_STILL: + split_at_seq_end = false; + /* First packet carries small-still sys_header */ + /* TODO No support mixed-mode stills sequences... */ + psstrm->CreateSysHeader (&sys_header, mux_rate, false, false, true, true, emux); + sys_header_ptr = &sys_header; + pack_header_ptr = &pack_header; + OutputPadding (false); + break; + + case MPEG_FORMAT_SVCD_STILL: + /* TODO: Video only at present */ + /* First packet carries video-info-only sys_header */ + psstrm->CreateSysHeader (&sys_header, mux_rate, false, true, true, true, vmux); + sys_header_ptr = &sys_header; + pack_header_ptr = &pack_header; + OutputPadding (false); + break; + + case MPEG_FORMAT_DVD: + /* A DVD System header is a weird thing. We seem to need to + include buffer info about streams 0xb8, 0xb9, 0xbf even if + they're not physically present but the buffers for the actual + video streams aren't included. + + TODO: I have no idead about MPEG audio streams if present... + */ + { + DummyMuxStream dvd_0xb9_strm_dummy (0xb9, 1, video_buffer_size); + DummyMuxStream dvd_0xb8_strm_dummy (0xb8, 0, 4096); + DummyMuxStream dvd_0xbf_strm_dummy (0xbf, 1, 2048); + + vector < MuxStream * >dvdmux; + vector < MuxStream * >::iterator muxstr; + dvdmux.push_back (&dvd_0xb9_strm_dummy); + dvdmux.push_back (&dvd_0xb8_strm_dummy); + unsigned int max_priv1_buffer = 0; + + for (muxstr = amux.begin (); muxstr < amux.end (); ++muxstr) { + // We mux *many* substreams on PRIVATE_STR_1 + // we set the system header buffer size to the maximum + // of all those we find + if ((*muxstr)->stream_id == PRIVATE_STR_1 && (*muxstr)->BufferSize () > max_priv1_buffer) { + max_priv1_buffer = (*muxstr)->BufferSize (); + } else + dvdmux.push_back (*muxstr); + } + + DummyMuxStream dvd_priv1_strm_dummy (PRIVATE_STR_1, 1, max_priv1_buffer); + + if (max_priv1_buffer > 0) + dvdmux.push_back (&dvd_priv1_strm_dummy); + + dvdmux.push_back (&dvd_0xbf_strm_dummy); + psstrm->CreateSysHeader (&sys_header, mux_rate, !vbr, false, true, true, dvdmux); + sys_header_ptr = &sys_header; + pack_header_ptr = &pack_header; + /* It is then followed up by a pair of PRIVATE_STR_2 packets which + we keep empty 'cos we don't know what goes there... + */ + } + break; + + default: + /* Create the in-stream header in case it is needed */ + psstrm->CreateSysHeader (&sys_header, mux_rate, !vbr, false, true, true, emux); + } +} + +/****************************************************************** + Program shutdown packets. Generate any irregular packets + needed at the end of the stream... + +******************************************************************/ + +void +OutputStream::OutputSuffix () +{ + psstrm->CreatePack (&pack_header, current_SCR, mux_rate); + psstrm->CreateSector (&pack_header, NULL, 0, pstrm, false, true, 0, 0, TIMESTAMPBITS_NO); +} + +/****************************************************************** + + Main multiplex iteration. + Opens and closes all needed files and manages the correct + call od the respective Video- and Audio- packet routines. + The basic multiplexing is done here. Buffer capacity and + Timestamp checking is also done here, decision is taken + wether we should genereate a Video-, Audio- or Padding- + packet. +******************************************************************/ + + + +bool +OutputStream::OutputMultiplex () +{ + for (;;) { + bool completion = true; + + for (str = estreams->begin (); str < estreams->end (); ++str) + completion &= (*str)->MuxCompleted (); + if (completion) + break; + + /* A little state-machine for handling the transition from one + segment to the next + */ + bool runout_incomplete; + VideoStream *master; + + switch (seg_state) { + + /* Audio and slave video access units at end of segment. + If there are any audio AU's whose PTS implies they + should be played *before* the video AU starting the + next segement is presented we mux them out. Once + they're gone we've finished this segment so we write + the suffix switch file, and start muxing a new segment. + */ + case runout_segment: + runout_incomplete = false; + for (str = estreams->begin (); str < estreams->end (); ++str) { + runout_incomplete |= !(*str)->RunOutComplete (); + } + + if (runout_incomplete) + break; + + /* Otherwise we write the stream suffix and start a new + stream file */ + OutputSuffix (); + psstrm->NextFile (); + + running_out = false; + seg_state = start_segment; + + /* Starting a new segment. + We send the segment prefix, video and audio reciever + buffers are assumed to start empty. We reset the segment + length count and hence the SCR. + + */ + + case start_segment: + mjpeg_info ("New sequence commences..."); + SetPosAndSCR (0); + MuxStatus (LOG_INFO); + + for (str = estreams->begin (); str < estreams->end (); ++str) { + (*str)->AllDemuxed (); + } + + packets_left_in_pack = packets_per_pack; + start_of_new_pack = true; + include_sys_header = sys_header_in_pack1; + buffers_in_video = always_buffers_in_video; + video_first = seg_starts_with_video & (vstreams.size () > 0); + OutputPrefix (); + + /* Set the offset applied to the raw PTS/DTS of AU's to + make the DTS of the first AU in the master (video) stream + precisely the video delay plus whatever time we wasted in + the sequence pre-amble. + + The DTS of the remaining streams are set so that + (modulo the relevant delay offset) they maintain the + same relative timing to the master stream. + + */ + + clockticks ZeroSCR; + + if (vstreams.size () != 0) + ZeroSCR = vstreams[0]->au->DTS; + else + ZeroSCR = (*estreams)[0]->au->DTS; + + for (str = vstreams.begin (); str < vstreams.end (); ++str) + (*str)->SetSyncOffset (video_delay + current_SCR - ZeroSCR); + for (str = astreams.begin (); str < astreams.end (); ++str) + (*str)->SetSyncOffset (audio_delay + current_SCR - ZeroSCR); + pstrm.nsec = 0; + for (str = estreams->begin (); str < estreams->end (); ++str) + (*str)->nsec = 0; + seg_state = mid_segment; + //for( str = estreams->begin(); str < estreams->end(); ++str ) + //{ + //mjpeg_info("STREAM %02x: SCR=%lld mux=%d reqDTS=%lld", + //(*str)->stream_id, + //current_SCR /300, + //(*str)->MuxPossible(current_SCR), + //(*str)->RequiredDTS()/300 + //); + //} + break; + + case mid_segment: + /* Once we exceed our file size limit, we need to + start a new file soon. If we want a single stream we + simply switch. + + Otherwise we're in the last gop of the current segment + (and need to start running streams out ready for a + clean continuation in the next segment). + TODO: runout_PTS really needs to be expressed in + sync delay adjusted units... + */ + + master = vstreams.size () > 0 ? static_cast < VideoStream * >(vstreams[0]) : 0; + if (psstrm->FileLimReached ()) { + if (opt_multifile_segment || master == 0) + psstrm->NextFile (); + else { + if (master->NextAUType () == IFRAME) { + seg_state = runout_segment; + runout_PTS = master->NextRequiredPTS (); + mjpeg_debug ("Running out to (raw) PTS %lld SCR=%lld", + runout_PTS / 300, current_SCR / 300); + running_out = true; + seg_state = runout_segment; + } + } + } else if (master != 0 && master->EndSeq ()) { + if (split_at_seq_end && master->Lookahead () != 0) { + if (!master->SeqHdrNext () || master->NextAUType () != IFRAME) { + mjpeg_error_exit1 ("Sequence split detected %d but no following sequence found...", + master->NextAUType ()); + } + + runout_PTS = master->NextRequiredPTS (); + mjpeg_debug ("Running out to %lld SCR=%lld", runout_PTS / 300, current_SCR / 300); + MuxStatus (LOG_INFO); + running_out = true; + seg_state = runout_segment; + } + } + break; + + } + + padding_packet = false; + start_of_new_pack = (packets_left_in_pack == packets_per_pack); + + for (str = estreams->begin (); str < estreams->end (); ++str) { + (*str)->DemuxedTo (current_SCR); + } + + + + // + // Find the ready-to-mux stream with the most urgent DTS + // + ElementaryStream *despatch = 0; + clockticks earliest = 0; + + for (str = estreams->begin (); str < estreams->end (); ++str) { + /* DEBUG + mjpeg_info("STREAM %02x: SCR=%lld mux=%d reqDTS=%lld", + (*str)->stream_id, + current_SCR /300, + (*str)->MuxPossible(current_SCR), + (*str)->RequiredDTS()/300 + ); + */ + if ((*str)->MuxPossible (current_SCR) && + (!video_first || (*str)->Kind () == ElementaryStream::video) + ) { + if (despatch == 0 || earliest > (*str)->RequiredDTS ()) { + despatch = *str; + earliest = (*str)->RequiredDTS (); + } + } + } + + if (underrun_ignore > 0) + --underrun_ignore; + + if (despatch) { + despatch->OutputSector (); + video_first = false; + if (current_SCR >= earliest && underrun_ignore == 0) { + mjpeg_warn ("Stream %02x: data will arrive too late sent(SCR)=%lld required(DTS)=%d", + despatch->stream_id, current_SCR / 300, (int) earliest / 300); + MuxStatus (LOG_WARN); + // Give the stream a chance to recover + underrun_ignore = 300; + ++underruns; + if (underruns > 10 && !opt_ignore_underrun) { + mjpeg_error_exit1 ("Too many frame drops -exiting"); + } + } + if (despatch->nsec > 50 && despatch->Lookahead () != 0 && !running_out) + despatch->UpdateBufferMinMax (); + padding_packet = false; + + } else { + // + // If we got here no stream could be muxed out. + // We therefore generate padding packets if necessary + // usually this is because reciever buffers are likely to be + // full. + // + if (vbr) { + // + // VBR: For efficiency we bump SCR up to five times or + // until it looks like buffer status will change + NextPosAndSCR (); + clockticks next_change = static_cast < clockticks > (0); + + for (str = estreams->begin (); str < estreams->end (); ++str) { + clockticks change_time = (*str)->bufmodel.NextChange (); + + if (next_change == 0 || change_time < next_change) + next_change = change_time; + } + unsigned int bumps = 5; + + while (bumps > 0 && next_change > current_SCR + ticks_per_sector) { + NextPosAndSCR (); + --bumps; + } + } else { + // Just output a padding packet + OutputPadding (false); + } + padding_packet = true; + } + + /* Update the counter for pack packets. VBR is a tricky + case as here padding packets are "virtual" */ + + if (!(vbr && padding_packet)) { + --packets_left_in_pack; + if (packets_left_in_pack == 0) + packets_left_in_pack = packets_per_pack; + } + + MuxStatus (LOG_DEBUG); + /* Unless sys headers are always required we turn them off after the first + packet has been generated */ + include_sys_header = always_sys_header_in_pack; + + pcomp = completed.begin (); + str = estreams->begin (); + while (str < estreams->end ()) { + if (!(*pcomp) && (*str)->MuxCompleted ()) { + mjpeg_info ("STREAM %02x completed @ %d.", (*str)->stream_id, (*str)->au->dorder); + MuxStatus (LOG_DEBUG); + (*pcomp) = true; + } + ++str; + ++pcomp; + } + + return true; + } + + return false; +} + + +void +OutputStream::Close () +{ + // Tidy up + OutputSuffix (); + psstrm->Close (); + mjpeg_info ("Multiplex completion at SCR=%lld.", current_SCR / 300); + MuxStatus (LOG_INFO); + for (str = estreams->begin (); str < estreams->end (); ++str) { + (*str)->Close (); + if ((*str)->nsec <= 50) + mjpeg_info ("BUFFERING stream too short for useful statistics"); + else + mjpeg_info ("BUFFERING min %d Buf max %d", (*str)->BufferMin (), (*str)->BufferMax ()); + } + + if (underruns > 0) { + mjpeg_error ("foo"); + mjpeg_error_exit1 ("MUX STATUS: Frame data under-runs detected!"); + } else { + mjpeg_info ("foo"); + mjpeg_info ("MUX STATUS: no under-runs detected."); + } +} + +/** + Calculate the packet payload of the output stream at a certain timestamp. +@param strm the output stream +@param buffers the number of buffers +@param PTSstamp presentation time stamp +@param DTSstamp decoding time stamp + */ +unsigned int +OutputStream::PacketPayload (MuxStream & strm, bool buffers, bool PTSstamp, bool DTSstamp) +{ + return psstrm->PacketPayload (strm, sys_header_ptr, pack_header_ptr, buffers, PTSstamp, DTSstamp) + - strm.StreamHeaderSize (); +} + +/*************************************************** + + WritePacket - Write out a normal packet carrying data from one of + the elementary stream being muxed. +@param max_packet_data_size the maximum packet data size allowed +@param strm output mux stream +@param buffers ? +@param PTSstamp presentation time stamp of the packet +@param DTSstamp decoding time stamp of the packet +@param timestamps ? +@param returns the written bytes/packets (?) +***************************************************/ + +unsigned int +OutputStream::WritePacket (unsigned int max_packet_data_size, + MuxStream & strm, + bool buffers, clockticks PTS, clockticks DTS, uint8_t timestamps) +{ + unsigned int written = psstrm->CreateSector (pack_header_ptr, + sys_header_ptr, + max_packet_data_size, + strm, + buffers, + false, + PTS, + DTS, + timestamps); + + NextPosAndSCR (); + return written; +} + +/*************************************************** + * + * WriteRawSector - Write out a packet carrying data for + * a control packet with irregular content. +@param rawsector data for the raw sector +@param length length of the raw sector + ***************************************************/ + +void +OutputStream::WriteRawSector (uint8_t * rawsector, unsigned int length) +{ + // + // Writing raw sectors when packs stretch over multiple sectors + // is a recipe for disaster! + // + assert (packets_per_pack == 1); + psstrm->RawWrite (rawsector, length); + NextPosAndSCR (); + +} + + + +/****************************************************************** + OutputPadding + + generates Pack/Sys Header/Packet information for a + padding stream and saves the sector + + We have to pass in a special flag to cope with appalling mess VCD + makes of audio packets (the last 20 bytes being dropped thing) 0 = + Fill the packet completetely. This include "audio packets" that + include no actual audio, only a system header and padding. +@param vcd_audio_pad flag for VCD audio padding +******************************************************************/ + + +void +OutputStream::OutputPadding (bool vcd_audio_pad) +{ + if (vcd_audio_pad) + psstrm->CreateSector (pack_header_ptr, sys_header_ptr, + 0, vcdapstrm, false, false, 0, 0, TIMESTAMPBITS_NO); + else + psstrm->CreateSector (pack_header_ptr, sys_header_ptr, + 0, pstrm, false, false, 0, 0, TIMESTAMPBITS_NO); + ++pstrm.nsec; + NextPosAndSCR (); + +} + + /****************************************************************** + * OutputGOPControlSector + * DVD System headers are carried in peculiar sectors carrying 2 + * PrivateStream2 packets. We're sticking 0's in the packets + * as we have no idea what's supposed to be in there. + * + * Thanks to Brent Byeler who worked out this work-around. + * + ******************************************************************/ + +void +OutputStream::OutputDVDPriv2 () +{ + uint8_t *packet_size_field; + uint8_t *index; + uint8_t sector_buf[sector_size]; + unsigned int tozero; + + assert (sector_size == 2048); + PS_Stream::BufferSectorHeader (sector_buf, pack_header_ptr, &sys_header, index); + PS_Stream::BufferPacketHeader (index, PRIVATE_STR_2, 2, // MPEG 2 + false, // No buffers + 0, 0, 0, // No timestamps + 0, TIMESTAMPBITS_NO, packet_size_field, index); + tozero = sector_buf + 1024 - index; + memset (index, 0, tozero); + index += tozero; + PS_Stream::BufferPacketSize (packet_size_field, index); + + PS_Stream::BufferPacketHeader (index, PRIVATE_STR_2, 2, // MPEG 2 + false, // No buffers + 0, 0, 0, // No timestamps + 0, TIMESTAMPBITS_NO, packet_size_field, index); + tozero = sector_buf + 2048 - index; + memset (index, 0, tozero); + index += tozero; + PS_Stream::BufferPacketSize (packet_size_field, index); + + WriteRawSector (sector_buf, sector_size); +} + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/outputstream.hh b/ext/mplex/outputstream.hh new file mode 100644 index 000000000..3b6256138 --- /dev/null +++ b/ext/mplex/outputstream.hh @@ -0,0 +1,198 @@ + +#ifndef __OUTPUTSTREAM_H__ +#define __OUTPUTSTREAM_H__ + +#include +#include +#include "mjpeg_types.h" + +#include "inputstrm.hh" +#include "padstrm.hh" +#include "systems.hh" + +typedef enum +{ start_segment, mid_segment, + runout_segment +} +segment_state; + +class OutputStream +{ +public: + OutputStream () + { + underrun_ignore = 0; + underruns = 0; + + opt_verbosity = 1; + opt_buffer_size = 46; + opt_data_rate = 0; /* 3486 = 174300B/sec would be right for VCD */ + opt_video_offset = 0; + opt_audio_offset = 0; + opt_sector_size = 2324; + opt_VBR = 0; + opt_mpeg = 1; + opt_mux_format = 0; /* Generic MPEG-1 stream as default */ + opt_multifile_segment = 0; + opt_always_system_headers = 0; + opt_packets_per_pack = 20; + opt_ignore_underrun = false; + opt_max_segment_size = 0; + } + bool OutputMultiplex (); + void Init (vector < ElementaryStream * >*strms, PS_Stream *stream); + void Close (); + + void InitSyntaxParameters (); + void ByteposTimecode (bitcount_t bytepos, clockticks & ts); + + inline Sys_header_struc *SystemHeader () + { + return &sys_header; + } + + unsigned int PacketPayload (MuxStream & strm, bool buffers, bool PTSstamp, bool DTSstamp); + unsigned int WritePacket (unsigned int max_packet_data_size, + MuxStream & strm, + bool buffers, clockticks PTS, clockticks DTS, uint8_t timestamps); + + /* Special "unusual" sector types needed for particular formats + */ + + void OutputDVDPriv2 (); + + /* Syntax control parameters, public becaus they're partly referenced + by the input stream objects. + */ + + bool always_sys_header_in_pack; + bool dtspts_for_all_vau; + bool sys_header_in_pack1; + bool buffers_in_video; + bool always_buffers_in_video; + bool buffers_in_audio; + bool always_buffers_in_audio; + bool sector_align_iframeAUs; + bool split_at_seq_end; + bool seg_starts_with_video; + bool timestamp_iframe_only; + bool video_buffers_iframe_only; + unsigned int audio_buffer_size; + unsigned int video_buffer_size; + + /* more profile options */ + int opt_verbosity; + int opt_quiet_mode; + int opt_buffer_size; + int opt_data_rate; + int opt_video_offset; + int opt_audio_offset; + int opt_sector_size; + int opt_VBR; + int opt_mpeg; + int opt_mux_format; + int opt_multifile_segment; + int opt_always_system_headers; + int opt_packets_per_pack; + bool opt_stills; + bool opt_ignore_underrun; + int verbose; + off_t opt_max_segment_size; + + /* Sequence run-out control */ + bool running_out; + clockticks runout_PTS; + + +/* In some situations the system/PES packets are embedded with + external transport data which has to be taken into account for SCR + calculations to be correct. E.g. VCD streams. Where each 2324 byte + system packet is embedded in a 2352 byte CD sector and the actual + MPEG data is preceded by 30 empty sectors. +*/ + + unsigned int sector_transport_size; + unsigned int transport_prefix_sectors; + unsigned int sector_size; + unsigned int vcd_zero_stuffing; /* VCD audio sectors have 20 0 bytes :-( */ + + int dmux_rate; /* Actual data mux-rate for calculations always a multiple of 50 */ + int mux_rate; /* MPEG mux rate (50 byte/sec units */ + unsigned int packets_per_pack; + +private: + + /* Stream packet component buffers */ + + Sys_header_struc sys_header; + Pack_struc pack_header; + Pack_struc *pack_header_ptr; + Sys_header_struc *sys_header_ptr; + bool start_of_new_pack; + bool include_sys_header; + + /* Under-run error messages */ + unsigned int underruns; + unsigned int underrun_ignore; + + /* Output data stream... */ + PS_Stream *psstrm; + bitcount_t bytes_output; + clockticks ticks_per_sector; + +public: + clockticks current_SCR; +private: + clockticks audio_delay; + clockticks video_delay; + bool vbr; + + /* Source data streams */ + /* Note: 1st video stream is regarded as the "master" stream for + the purpose of splitting sequences etc... + */ + vector < ElementaryStream * >*estreams; // Complete set + vector < ElementaryStream * >vstreams; // Video streams in estreams + vector < ElementaryStream * >astreams; // Audio streams in estreams + + PaddingStream pstrm; + VCDAPadStream vcdapstrm; + DVDPriv2Stream dvdpriv2strm; + +private: + unsigned int RunInSectors (); + + void NextPosAndSCR (); + void SetPosAndSCR (bitcount_t bytepos); + + void OutputPrefix (); + + void OutputSuffix (); + void OutputPadding (bool vcd_audio_pad); + void MuxStatus (log_level_t level); + + void WriteRawSector (uint8_t * rawpackets, unsigned int length); + void AppendMuxStreamsOf (vector < ElementaryStream * >&elem, vector < MuxStream * >&mux); + + /* state var for muxing */ + segment_state seg_state; + + vector < bool > completed; + vector < bool >::iterator pcomp; + vector < ElementaryStream * >::iterator str; + + unsigned int packets_left_in_pack; /* Suppress warning */ + bool padding_packet; + bool video_first; + +/* + * Local variables: + * c-file-style: "gnu" + * tab-width: 8 + * indent-tabs-mode: nil + * End: + */ +}; + + +#endif //__OUTPUTSTREAM_H__ diff --git a/ext/mplex/padstrm.cc b/ext/mplex/padstrm.cc new file mode 100644 index 000000000..95e32dbac --- /dev/null +++ b/ext/mplex/padstrm.cc @@ -0,0 +1,59 @@ + +/* + * padstrm.cc: Padding stream pseudo-input streams + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "padstrm.hh" + + + +// +// Generator for padding packets in a padding stream... +// + + +unsigned int +PaddingStream::ReadPacketPayload (uint8_t * dst, unsigned int to_read) +{ + memset (dst, STUFFING_BYTE, to_read); + return to_read; +} + +unsigned int +VCDAPadStream::ReadPacketPayload (uint8_t * dst, unsigned int to_read) +{ + memset (dst, STUFFING_BYTE, to_read); + return to_read; +} + +unsigned int +DVDPriv2Stream::ReadPacketPayload (uint8_t * dst, unsigned int to_read) +{ + memset (dst, 0, to_read); + return to_read; +} + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/padstrm.hh b/ext/mplex/padstrm.hh new file mode 100644 index 000000000..3300e5b68 --- /dev/null +++ b/ext/mplex/padstrm.hh @@ -0,0 +1,73 @@ + +/* + * padstrm.hh: Padding stream pseudo input-streamsin + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __PADSTRM_H__ +#define __PADSTRM_H__ + +#include "inputstrm.hh" + + +class PaddingStream:public MuxStream +{ +public: + PaddingStream () + { + MuxStream::Init (PADDING_STR, 0, 0, 0, false, false); + } + + unsigned int ReadPacketPayload (uint8_t * dst, unsigned int to_read); +}; + +class VCDAPadStream:public MuxStream +{ +public: + VCDAPadStream () + { + Init (PADDING_STR, 0, 0, 20, false, false); + + } + + unsigned int ReadPacketPayload (uint8_t * dst, unsigned int to_read); +}; + +class DVDPriv2Stream:public MuxStream +{ +public: + DVDPriv2Stream () + { + Init (PRIVATE_STR_2, 0, 0, 0, false, false); + + } + + unsigned int ReadPacketPayload (uint8_t * dst, unsigned int to_read); +}; + + +#endif // __PADSTRM_H__ + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/stillsstream.cc b/ext/mplex/stillsstream.cc new file mode 100644 index 000000000..95c895052 --- /dev/null +++ b/ext/mplex/stillsstream.cc @@ -0,0 +1,193 @@ + +/* + * stillsstreams.c: Class for elemenary still video streams + * Most functionality is inherited from VideoStream + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include + +#include "stillsstream.hh" +#include "outputstream.hh" + +void +StillsStream::Init () +{ + int stream_id = 0; + int buffer_size = 0; + + SetBufSize (4 * 1024 * 1024); + InitAUbuffer (); + ScanFirstSeqHeader (); + + mjpeg_debug ("SETTING video buffer to %d", muxinto.video_buffer_size); + switch (opt_mux_format) { + case MPEG_FORMAT_VCD_STILL: + if (horizontal_size > 352) { + stream_id = VIDEO_STR_0 + 2; + buffer_size = vbv_buffer_size * 2048; + mjpeg_info ("Stills Stream %02x: high-resolution VCD stills %d KB each", + stream_id, buffer_size); + if (buffer_size < 46 * 1024) + mjpeg_error_exit1 + ("I Can't multiplex high-res stills smaller than normal res stills - sorry!"); + + } else { + stream_id = VIDEO_STR_0 + 1; + buffer_size = 46 * 1024; + mjpeg_info ("Stills Stream %02x: normal VCD stills", stream_id); + } + break; + case MPEG_FORMAT_SVCD_STILL: + if (horizontal_size > 480) { + stream_id = VIDEO_STR_0 + 1; + buffer_size = 230 * 1024; + mjpeg_info ("Stills Stream %02x: high-resolution SVCD stills.", stream_id); + } else { + stream_id = VIDEO_STR_0 + 1; + buffer_size = 230 * 1024; + mjpeg_info ("Stills Stream %02x: normal-resolution SVCD stills.", stream_id); + } + break; + } + + + MuxStream::Init (stream_id, 1, // Buffer scale + buffer_size, 0, // Zero stuffing + muxinto.buffers_in_video, muxinto.always_buffers_in_video); + + /* Skip to the end of the 1st AU (*2nd* Picture start!) + */ + AU_hdr = SEQUENCE_HEADER; + AU_pict_data = 0; + AU_start = 0LL; + + OutputSeqhdrInfo (); + +} + + + + +/* + * Compute DTS / PTS for a VCD/SVCD Stills sequence + * TODO: Very crude. Simply assumes each still stays for the specified + * frame interval and that enough run-in delay is present for the first + * frame to be loaded. + * + */ + +void +StillsStream::NextDTSPTS (clockticks & DTS, clockticks & PTS) +{ + clockticks interval = static_cast < clockticks > + (intervals->NextFrameInterval () * CLOCKS / frame_rate); + clockticks time_for_xfer; + + muxinto.ByteposTimecode (BufferSize (), time_for_xfer); + + DTS = current_PTS + time_for_xfer; // This frame decoded just after + // Predecessor completed. + PTS = current_PTS + time_for_xfer + interval; + current_PTS = PTS; + current_DTS = DTS; +} + +/* + * VCD mixed stills segment items have the constraint that both stills + * streams must end together. To do this each stream has to know + * about its "sibling". + * + */ + +void +VCDStillsStream::SetSibling (VCDStillsStream * _sibling) +{ + assert (_sibling != 0); + sibling = _sibling; + if (sibling->stream_id == stream_id) { + mjpeg_error_exit1 ("VCD mixed stills stream cannot contain two streams of the same type!"); + } + +} + +/* + * Check if we've reached the last sector of the last AU. Note: that + * we know no PTS/DTS time-stamps will be needed because no new AU + * will appear in the last sector. WARNING: We assume a still won't + * fit into a single secotr. + * + */ + +bool +VCDStillsStream::LastSectorLastAU () +{ + return (Lookahead () == 0 && + au_unsent <= muxinto.PacketPayload (*this, buffers_in_header, false, false) + ); +} + + +/* + * The requirement that VCD mixed stills segment items constituent streams + * end together means we can't mux the last sector of the last AU of + * such streams until its sibling has already completed muxing or is + * also ready to mux the last sector of its last AU. + * + * NOTE: Will not work right if sector_align_iframe_AUs not set as this + * will allow multiple AU's in a sector. + * + */ + + +bool +VCDStillsStream::MuxPossible () +{ + if (bufmodel.Size () < au_unsent) { + mjpeg_error_exit1 + ("Illegal VCD still: larger than maximum permitted by its buffering parameters!"); + } + if (RunOutComplete () || bufmodel.Space () < au_unsent) { + return false; + } + + if (LastSectorLastAU ()) { + if (sibling != 0) { + if (!stream_mismatch_warned && sibling->NextAUType () != NOFRAME) { + mjpeg_warn ("One VCD stills stream runs significantly longer than the other!"); + mjpeg_warn ("Simultaneous stream ending recommended by standard not possible"); + return true; + } + return sibling->MuxCompleted () || sibling->LastSectorLastAU (); + } else + return true; + } else + return true; +} + + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/stillsstream.hh b/ext/mplex/stillsstream.hh new file mode 100644 index 000000000..bfd2aca57 --- /dev/null +++ b/ext/mplex/stillsstream.hh @@ -0,0 +1,107 @@ + +/* + * stillsstreams.c: Class for elemenary still video streams + * Most functionality is inherited from VideoStream + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "videostrm.hh" + +class FrameIntervals +{ +public: + virtual int NextFrameInterval () = 0; +}; + +// +// Class of sequence of frame intervals. +// + +class ConstantFrameIntervals:public FrameIntervals +{ +public: + ConstantFrameIntervals (int _frame_interval):frame_interval (_frame_interval) + { + } + int NextFrameInterval () + { + return frame_interval; + }; +private: + int frame_interval; +}; + + +// +// Class for video stills sequence for (S)VCD non-mixed stills segment +// item +// + +class StillsStream:public VideoStream +{ +public: + StillsStream (IBitStream & ibs, + OutputStream & into, FrameIntervals * frame_ints):VideoStream (ibs, into), + current_PTS (0LL), current_DTS (0LL), intervals (frame_ints) + { + } + void Init (); + +private: + virtual void NextDTSPTS (clockticks & DTS, clockticks & PTS); + clockticks current_PTS; + clockticks current_DTS; + FrameIntervals *intervals; + + int opt_mux_format; +}; + +// +// Class for video stills sequence for VCD mixed stills Segment item. +// + +class VCDStillsStream:public StillsStream +{ +public: + VCDStillsStream (IBitStream & ibs, + OutputStream & into, FrameIntervals * frame_ints):StillsStream (ibs, into, + frame_ints), + sibling (0), stream_mismatch_warned (false) + { + } + + void SetSibling (VCDStillsStream *); + virtual bool MuxPossible (); + +private: + bool LastSectorLastAU (); + VCDStillsStream *sibling; + bool stream_mismatch_warned; +}; + + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/systems.cc b/ext/mplex/systems.cc new file mode 100644 index 000000000..8aa393790 --- /dev/null +++ b/ext/mplex/systems.cc @@ -0,0 +1,761 @@ +#include +#include +#include +#include +#include +#include +#include "systems.hh" +#include "mplexconsts.hh" + +uint8_t dummy_buf[8000]; +void +PS_Stream::Init (unsigned _mpeg, unsigned int _sector_size, off_t max_seg_size) +{ + max_segment_size = max_seg_size; + mpeg_version = _mpeg; + sector_size = _sector_size; + segment_num = 1; + written = 0; + + sector_buf = new uint8_t[_sector_size]; +} + +bool PS_Stream::FileLimReached () +{ + return max_segment_size != 0 && written > max_segment_size; +} + +void +PS_Stream::NextFile () +{ +/* + char prev_filename[strlen (cur_filename) + 1]; + + //fclose (strm); + ++segment_num; + strcpy (prev_filename, cur_filename); + snprintf (cur_filename, MAXPATHLEN, filename_pat, segment_num); + if (strcmp (prev_filename, cur_filename) == 0) { + mjpeg_error_exit1 + ("Need to split output but there appears to be no %%d in the filename pattern %s", + filename_pat); + } + strm = fopen (cur_filename, "wb"); + if (strm == NULL) { + mjpeg_error_exit1 ("Could not open for writing: %s", cur_filename); + } + */ +} + +/************************************************************** + + Packet payload compute how much payload a sector-sized packet with the + specified headers can carry... + TODO: Should really be called "Sector Payload" +**************************************************************/ + + +unsigned int +PS_Stream::PacketPayload (MuxStream & strm, + Sys_header_struc * sys_header, + Pack_struc * pack_header, int buffers, int PTSstamp, int DTSstamp) +{ + int payload = sector_size - (PACKET_HEADER_SIZE + strm.zero_stuffing); + + if (sys_header != NULL) + payload -= sys_header->length; + if (mpeg_version == 2) { + if (buffers) + payload -= MPEG2_BUFFERINFO_LENGTH; + + payload -= MPEG2_AFTER_PACKET_LENGTH_MIN; + if (pack_header != NULL) + payload -= pack_header->length; + if (DTSstamp) + payload -= DTS_PTS_TIMESTAMP_LENGTH; + if (PTSstamp) + payload -= DTS_PTS_TIMESTAMP_LENGTH; + + } else { + if (buffers) + payload -= MPEG1_BUFFERINFO_LENGTH; + + payload -= MPEG1_AFTER_PACKET_LENGTH_MIN; + if (pack_header != NULL) + payload -= pack_header->length; + if (DTSstamp) + payload -= DTS_PTS_TIMESTAMP_LENGTH; + if (PTSstamp) + payload -= DTS_PTS_TIMESTAMP_LENGTH; + if (DTSstamp || PTSstamp) + payload += 1; /* No need for nostamp marker ... */ + + } + + return payload; +} + + + +/************************************************************************* + Kopiert einen TimeCode in einen Bytebuffer. Dabei wird er nach + MPEG-Verfahren in bits aufgesplittet. + + Makes a Copy of a TimeCode in a Buffer, splitting it into bitfields + for MPEG-1/2 DTS/PTS fields and MPEG-1 pack scr fields +*************************************************************************/ + +void +PS_Stream::BufferDtsPtsMpeg1ScrTimecode (clockticks timecode, uint8_t marker, uint8_t ** buffer) +{ + clockticks thetime_base; + uint8_t temp; + unsigned int msb, lsb; + + /* MPEG-1 uses a 90KHz clock, extended to 300*90KHz = 27Mhz in MPEG-2 */ + /* For these fields we only encode to MPEG-1 90Khz resolution... */ + + thetime_base = timecode / 300; + msb = (thetime_base >> 32) & 1; + lsb = (thetime_base & 0xFFFFFFFFLL); + + temp = (marker << 4) | (msb << 3) | ((lsb >> 29) & 0x6) | 1; + *((*buffer)++) = temp; + temp = (lsb & 0x3fc00000) >> 22; + *((*buffer)++) = temp; + temp = ((lsb & 0x003f8000) >> 14) | 1; + *((*buffer)++) = temp; + temp = (lsb & 0x7f80) >> 7; + *((*buffer)++) = temp; + temp = ((lsb & 0x007f) << 1) | 1; + *((*buffer)++) = temp; + +} + +/************************************************************************* + Makes a Copy of a TimeCode in a Buffer, splitting it into bitfields + for MPEG-2 pack scr fields which use the full 27Mhz resolution + + Did they *really* need to put a 27Mhz + clock source into the system stream. Does anyone really need it + for their decoders? Get real... I guess they thought it might allow + someone somewhere to save on a proper clock circuit. +*************************************************************************/ + + +void +PS_Stream::BufferMpeg2ScrTimecode (clockticks timecode, uint8_t ** buffer) +{ + clockticks thetime_base; + unsigned int thetime_ext; + uint8_t temp; + unsigned int msb, lsb; + + thetime_base = timecode / 300; + thetime_ext = timecode % 300; + msb = (thetime_base >> 32) & 1; + lsb = thetime_base & 0xFFFFFFFFLL; + + + temp = (MARKER_MPEG2_SCR << 6) | (msb << 5) | ((lsb >> 27) & 0x18) | 0x4 | ((lsb >> 28) & 0x3); + *((*buffer)++) = temp; + temp = (lsb & 0x0ff00000) >> 20; + *((*buffer)++) = temp; + temp = ((lsb & 0x000f8000) >> 12) | 0x4 | ((lsb & 0x00006000) >> 13); + *((*buffer)++) = temp; + temp = (lsb & 0x00001fe0) >> 5; + *((*buffer)++) = temp; + temp = ((lsb & 0x0000001f) << 3) | 0x4 | ((thetime_ext & 0x00000180) >> 7); + *((*buffer)++) = temp; + temp = ((thetime_ext & 0x0000007F) << 1) | 1; + *((*buffer)++) = temp; +} + +/************************************************************************* + +BufferPaddingPacket - Insert a padding packet of the desired length + into the specified Program/System stream buffer + +**************************************************************************/ + +void +PS_Stream::BufferPaddingPacket (int padding, uint8_t * &buffer) +{ + uint8_t *index = buffer; + int i; + + assert ((mpeg_version == 2 && padding >= 6) || (mpeg_version == 1 && padding >= 7)); + + *(index++) = static_cast < uint8_t > (PACKET_START) >> 16; + *(index++) = static_cast < uint8_t > (PACKET_START & 0x00ffff) >> 8; + *(index++) = static_cast < uint8_t > (PACKET_START & 0x0000ff); + *(index++) = PADDING_STR; + *(index++) = static_cast < uint8_t > ((padding - 6) >> 8); + *(index++) = static_cast < uint8_t > ((padding - 6) & 0xff); + if (mpeg_version == 2) { + for (i = 0; i < padding - 6; i++) + *(index++) = static_cast < uint8_t > (STUFFING_BYTE); + } else { + *(index++) = 0x0F; + for (i = 0; i < padding - 7; i++) + *(index++) = static_cast < uint8_t > (STUFFING_BYTE); + } + + buffer = index; +} + + +void +PS_Stream::BufferSectorHeader (uint8_t * index, + Pack_struc * pack, + Sys_header_struc * sys_header, uint8_t * &header_end) +{ + /* Pack header if present */ + + if (pack != NULL) { + memcpy (index, pack->buf, pack->length); + index += pack->length; + } + + /* System header if present */ + + if (sys_header != NULL) { + memcpy (index, sys_header->buf, sys_header->length); + index += sys_header->length; + } + header_end = index; +} + +/****************************************** + * + * BufferPacketHeader + * Construct and MPEG-1/2 header for a packet in the specified + * buffer (which *MUST* be long enough) and set points to the start of + * the payload and packet length fields. + * + ******************************************/ + + +void +PS_Stream::BufferPacketHeader (uint8_t * buf, + uint8_t type, + unsigned int mpeg_version, + bool buffers, + unsigned int buffer_size, + uint8_t buffer_scale, + clockticks PTS, + clockticks DTS, + uint8_t timestamps, uint8_t * &size_field, uint8_t * &header_end) +{ + + uint8_t *index = buf; + uint8_t *pes_header_len_field = 0; + + + /* konstante Packet Headerwerte eintragen */ + /* write constant packet header data */ + + *(index++) = static_cast < uint8_t > (PACKET_START) >> 16; + *(index++) = static_cast < uint8_t > (PACKET_START & 0x00ffff) >> 8; + *(index++) = static_cast < uint8_t > (PACKET_START & 0x0000ff); + *(index++) = type; + + + /* we remember this offset so we can fill in the packet size field once + we know the actual size... */ + size_field = index; + index += 2; + + if (mpeg_version == 1) { + /* MPEG-1: buffer information */ + if (buffers) { + *(index++) = static_cast < uint8_t > (0x40 | (buffer_scale << 5) | (buffer_size >> 8)); + *(index++) = static_cast < uint8_t > (buffer_size & 0xff); + } + + /* MPEG-1: PTS, PTS & DTS, oder gar nichts? */ + /* should we write PTS, PTS & DTS or nothing at all ? */ + + switch (timestamps) { + case TIMESTAMPBITS_NO: + *(index++) = MARKER_NO_TIMESTAMPS; + break; + case TIMESTAMPBITS_PTS: + BufferDtsPtsMpeg1ScrTimecode (PTS, MARKER_JUST_PTS, &index); + break; + case TIMESTAMPBITS_PTS_DTS: + BufferDtsPtsMpeg1ScrTimecode (PTS, MARKER_PTS, &index); + BufferDtsPtsMpeg1ScrTimecode (DTS, MARKER_DTS, &index); + break; + } + } else if (type != PADDING_STR) { + /* MPEG-2 packet syntax header flags. */ + /* These *DO NOT* appear in padding packets */ + /* TODO: They don't appear in several others either! */ + /* First byte: + <1,0> + */ + *(index++) = 0x81; + /* Second byte: PTS PTS_DTS or neither? Buffer info? + + + */ + *(index++) = (timestamps << 6) | (!!buffers); + /* Third byte: + */ + pes_header_len_field = index; /* To fill in later! */ + index++; + /* MPEG-2: the timecodes if required */ + switch (timestamps) { + case TIMESTAMPBITS_PTS: + BufferDtsPtsMpeg1ScrTimecode (PTS, MARKER_JUST_PTS, &index); + break; + + case TIMESTAMPBITS_PTS_DTS: + BufferDtsPtsMpeg1ScrTimecode (PTS, MARKER_PTS, &index); + BufferDtsPtsMpeg1ScrTimecode (DTS, MARKER_DTS, &index); + break; + } + + /* MPEG-2 The buffer information in a PES_extension */ + if (buffers) { + /* MPEG-2 PES extension header + + + <{PES_extension_flag_2=0> */ + *(index++) = static_cast < uint8_t > (0x1e); + *(index++) = static_cast < uint8_t > (0x40 | (buffer_scale << 5) | (buffer_size >> 8)); + *(index++) = static_cast < uint8_t > (buffer_size & 0xff); + } + } + + if (mpeg_version == 2 && type != PADDING_STR) { + *pes_header_len_field = static_cast < uint8_t > (index - (pes_header_len_field + 1)); + } + + header_end = index; +} + +/************************************************************************* + * CreateSector + * + * Creates a complete sector to carry a padding packet or a packet + * from one of the elementary streams. Pack and System headers are + * prepended if required. + * + * We allow for situations where want to + * deliberately reduce the payload carried by stuffing. + * This allows us to deal with tricky situations where the + * header overhead of adding in additional information + * would exceed the remaining payload capacity. + * + * Header stuffing and/or a padding packet is appended if the sector is + * unfilled. Zero stuffing after the end of a packet is also supported + * to allow thos wretched audio packets from VCD's to be handled. + * + * TODO: Should really be called "WriteSector" + * TODO: We need to add a mechanism for sub-headers of private streams + * to be generated... + * + *************************************************************************/ + + +unsigned int +PS_Stream::CreateSector (Pack_struc * pack, + Sys_header_struc * sys_header, + unsigned int max_packet_data_size, + MuxStream & strm, + bool buffers, + bool end_marker, clockticks PTS, clockticks DTS, uint8_t timestamps) +{ + unsigned int i; + unsigned int j; + uint8_t *index; + uint8_t *size_offset; + uint8_t *fixed_packet_header_end; + uint8_t *pes_header_len_offset = 0; + unsigned int target_packet_data_size; + unsigned int actual_packet_data_size; + int packet_data_to_read; + unsigned int bytes_short; + uint8_t type = strm.stream_id; + uint8_t buffer_scale = strm.BufferScale (); + unsigned int buffer_size = strm.BufferSizeCode (); + unsigned int sector_pack_area; + + index = sector_buf; + + sector_pack_area = sector_size - strm.zero_stuffing; + if (end_marker) + sector_pack_area -= 4; + + BufferSectorHeader (index, pack, sys_header, index); + + /* konstante Packet Headerwerte eintragen */ + /* write constant packet header data */ + + *(index++) = static_cast < uint8_t > (PACKET_START) >> 16; + *(index++) = static_cast < uint8_t > (PACKET_START & 0x00ffff) >> 8; + *(index++) = static_cast < uint8_t > (PACKET_START & 0x0000ff); + *(index++) = type; + + + /* we remember this offset so we can fill in the packet size field once + we know the actual size... */ + size_offset = index; + index += 2; + fixed_packet_header_end = index; + + if (mpeg_version == 1) { + /* MPEG-1: buffer information */ + if (buffers) { + *(index++) = static_cast < uint8_t > (0x40 | (buffer_scale << 5) | (buffer_size >> 8)); + *(index++) = static_cast < uint8_t > (buffer_size & 0xff); + } + + /* MPEG-1: PTS, PTS & DTS, oder gar nichts? */ + /* should we write PTS, PTS & DTS or nothing at all ? */ + + switch (timestamps) { + case TIMESTAMPBITS_NO: + *(index++) = MARKER_NO_TIMESTAMPS; + break; + case TIMESTAMPBITS_PTS: + BufferDtsPtsMpeg1ScrTimecode (PTS, MARKER_JUST_PTS, &index); + break; + case TIMESTAMPBITS_PTS_DTS: + BufferDtsPtsMpeg1ScrTimecode (PTS, MARKER_PTS, &index); + BufferDtsPtsMpeg1ScrTimecode (DTS, MARKER_DTS, &index); + break; + } + } else if (type != PADDING_STR) { + /* MPEG-2 packet syntax header flags. */ + /* These *DO NOT* appear in padding packets */ + /* TODO: They don't appear in several others either! */ + /* First byte: + <1,0> + */ + *(index++) = 0x81; + /* Second byte: PTS PTS_DTS or neither? Buffer info? + + + */ + *(index++) = (timestamps << 6) | (!!buffers); + /* Third byte: + */ + pes_header_len_offset = index; /* To fill in later! */ + index++; + /* MPEG-2: the timecodes if required */ + switch (timestamps) { + case TIMESTAMPBITS_PTS: + BufferDtsPtsMpeg1ScrTimecode (PTS, MARKER_JUST_PTS, &index); + break; + + case TIMESTAMPBITS_PTS_DTS: + BufferDtsPtsMpeg1ScrTimecode (PTS, MARKER_PTS, &index); + BufferDtsPtsMpeg1ScrTimecode (DTS, MARKER_DTS, &index); + break; + } + + /* MPEG-2 The buffer information in a PES_extension */ + if (buffers) { + /* MPEG-2 PES extension header + + + <{PES_extension_flag_2=0> */ + *(index++) = static_cast < uint8_t > (0x1e); + *(index++) = static_cast < uint8_t > (0x40 | (buffer_scale << 5) | (buffer_size >> 8)); + *(index++) = static_cast < uint8_t > (buffer_size & 0xff); + } + } +#ifdef MUX_DEBUG + // DVD MPEG2: AC3 in PRIVATE_STR_1 + if (type == PRIVATE_STR_1) { + ac3_header = index; + // TODO: should allow multiple AC3 streams... + //ac3_header[0] = AC3_SUB_STR_1; // byte: Audio stream number + // byte: num of AC3 syncwords + // byte: Offset first AC3 syncword (hi) + // byte: Offset 2nd AC2 syncword (lo) + //index += 4; + //subheader_size = 4; + subheader_size = 0; + } else +#endif + + + /* MPEG-1, MPEG-2: data available to be filled is packet_size less + * header and MPEG-1 trailer... */ + + target_packet_data_size = sector_pack_area - (index - sector_buf); + + + /* DEBUG: A handy consistency check when we're messing around */ +#ifdef MUX_DEBUG + if (type != PADDING_STR && (end_marker ? target_packet_data_size + 4 : target_packet_data_size) + != + PacketPayload (strm, sys_header, pack, buffers, + timestamps & TIMESTAMPBITS_PTS, timestamps & TIMESTAMPBITS_DTS)) + { + printf ("\nPacket size calculation error %d S%d P%d B%d %d %d!\n ", + timestamps, + sys_header != 0, pack != 0, buffers, + target_packet_data_size, + PacketPayload (strm, sys_header, pack, buffers, + timestamps & TIMESTAMPBITS_PTS, timestamps & TIMESTAMPBITS_DTS)); + exit (1); + } +#endif + + /* If a maximum payload data size is specified (!=0) and is + smaller than the space available thats all we read (the + remaining space is stuffed) */ + if (max_packet_data_size != 0 && max_packet_data_size < target_packet_data_size) { + packet_data_to_read = max_packet_data_size; + } else + packet_data_to_read = target_packet_data_size; + + + /* MPEG-1, MPEG-2: read in available packet data ... */ + + actual_packet_data_size = strm.ReadPacketPayload (index, packet_data_to_read); + + // DVD MPEG2: AC3 in PRIVATE_STR_1: fill in syncword count and offset +#ifdef MUX_DEBUG + if (type == PRIVATE_STR_1) { + unsigned int syncwords_found; + + for (i = 0; i < actual_packet_data_size; ++i) { + if (index[i + 4] == 0x0b && i + 5 < actual_packet_data_size && index[i + 5] == 0x77) { + if (syncwords_found == 0) { + if (ac3_header[2] != static_cast < uint8_t > ((i + 1) >> 8) || + ac3_header[3] != static_cast < uint8_t > ((i + 1) & 0xff)) + printf ("BROKEN HEADER %2x %2x (%2x %2x)\n", + ac3_header[2], + ac3_header[3], + static_cast < uint8_t > ((i + 1) >> 8), + static_cast < uint8_t > ((i + 1) & 0xff)); + } + ++syncwords_found; + } + } + } +#endif + bytes_short = target_packet_data_size - actual_packet_data_size; + + /* Handle the situations where we don't have enough data to fill + the packet size fully ... + Small shortfalls are dealt with by stuffing, big ones by inserting + padding packets. + */ + + + if (bytes_short < MINIMUM_PADDING_PACKET_SIZE && bytes_short > 0) { + if (mpeg_version == 1) { + /* MPEG-1 stuffing happens *before* header data fields. */ + memmove (fixed_packet_header_end + bytes_short, + fixed_packet_header_end, actual_packet_data_size + (index - fixed_packet_header_end) + ); + for (j = 0; j < bytes_short; ++j) + fixed_packet_header_end[j] = static_cast < uint8_t > (STUFFING_BYTE); + } else { + memmove (index + bytes_short, index, actual_packet_data_size); + for (j = 0; j < bytes_short; ++j) + *(index + j) = static_cast < uint8_t > (STUFFING_BYTE); + } + index += bytes_short; + bytes_short = 0; + } + + + /* MPEG-2: we now know the header length... but we mustn't forget + to take into account any non-MPEG headers we've included. + Currently this only happens for AC3 audio, but who knows... + */ + if (mpeg_version == 2 && type != PADDING_STR) { + unsigned int pes_header_len = index - (pes_header_len_offset + 1); + + *pes_header_len_offset = static_cast < uint8_t > (pes_header_len); + } + index += actual_packet_data_size; + /* MPEG-1, MPEG-2: Now we know that actual packet size */ + size_offset[0] = static_cast < uint8_t > ((index - size_offset - 2) >> 8); + size_offset[1] = static_cast < uint8_t > ((index - size_offset - 2) & 0xff); + + /* The case where we have fallen short enough to allow it to be + dealt with by inserting a stuffing packet... */ + if (bytes_short != 0) { + *(index++) = static_cast < uint8_t > (PACKET_START) >> 16; + *(index++) = static_cast < uint8_t > (PACKET_START & 0x00ffff) >> 8; + *(index++) = static_cast < uint8_t > (PACKET_START & 0x0000ff); + *(index++) = PADDING_STR; + *(index++) = static_cast < uint8_t > ((bytes_short - 6) >> 8); + *(index++) = static_cast < uint8_t > ((bytes_short - 6) & 0xff); + if (mpeg_version == 2) { + for (i = 0; i < bytes_short - 6; i++) + *(index++) = static_cast < uint8_t > (STUFFING_BYTE); + } else { + *(index++) = 0x0F; /* TODO: A.Stevens 2000 Why is this here? */ + for (i = 0; i < bytes_short - 7; i++) + *(index++) = static_cast < uint8_t > (STUFFING_BYTE); + } + + bytes_short = 0; + } + + if (end_marker) { + *(index++) = static_cast < uint8_t > ((ISO11172_END) >> 24); + *(index++) = static_cast < uint8_t > ((ISO11172_END & 0x00ff0000) >> 16); + *(index++) = static_cast < uint8_t > ((ISO11172_END & 0x0000ff00) >> 8); + *(index++) = static_cast < uint8_t > (ISO11172_END & 0x000000ff); + } + + for (i = 0; i < strm.zero_stuffing; i++) + *(index++) = static_cast < uint8_t > (0); + + + /* At this point padding or stuffing will have ensured the packet + is filled to target_packet_data_size + */ + RawWrite (sector_buf, sector_size); + return actual_packet_data_size; +} + + + + +/************************************************************************* + Create_Pack + erstellt in einem Buffer die spezifischen Pack-Informationen. + Diese werden dann spaeter von der Sector-Routine nochmals + in dem Sektor kopiert. + + writes specifical pack header information into a buffer + later this will be copied from the sector routine into + the sector buffer +*************************************************************************/ + +void +PS_Stream::CreatePack (Pack_struc * pack, clockticks SCR, unsigned int mux_rate) +{ + uint8_t *index; + + index = pack->buf; + + *(index++) = static_cast < uint8_t > ((PACK_START) >> 24); + *(index++) = static_cast < uint8_t > ((PACK_START & 0x00ff0000) >> 16); + *(index++) = static_cast < uint8_t > ((PACK_START & 0x0000ff00) >> 8); + *(index++) = static_cast < uint8_t > (PACK_START & 0x000000ff); + + if (mpeg_version == 2) { + /* Annoying: MPEG-2's SCR pack header time is different from + all the rest... */ + BufferMpeg2ScrTimecode (SCR, &index); + *(index++) = static_cast < uint8_t > (mux_rate >> 14); + *(index++) = static_cast < uint8_t > (0xff & (mux_rate >> 6)); + *(index++) = static_cast < uint8_t > (0x03 | ((mux_rate & 0x3f) << 2)); + *(index++) = static_cast < uint8_t > (RESERVED_BYTE << 3 | 0); /* No pack stuffing */ + } else { + BufferDtsPtsMpeg1ScrTimecode (SCR, MARKER_MPEG1_SCR, &index); + *(index++) = static_cast < uint8_t > (0x80 | (mux_rate >> 15)); + *(index++) = static_cast < uint8_t > (0xff & (mux_rate >> 7)); + *(index++) = static_cast < uint8_t > (0x01 | ((mux_rate & 0x7f) << 1)); + } + pack->SCR = SCR; + pack->length = index - pack->buf; +} + + +/************************************************************************* + Create_Sys_Header + erstelle in einem Buffer die spezifischen Sys_Header + Informationen. Diese werden spaeter von der Sector-Routine + nochmals zum Sectorbuffer kopiert. + + writes specifical system header information into a buffer + later this will be copied from the sector routine into + the sector buffer + RETURN: Length of header created... +*************************************************************************/ + +void +PS_Stream::CreateSysHeader (Sys_header_struc * sys_header, + unsigned int rate_bound, + bool fixed, + int CSPS, + bool audio_lock, bool video_lock, vector < MuxStream * >&streams) +{ + uint8_t *index; + uint8_t *len_index; + int system_header_size; + + index = sys_header->buf; + int video_bound = 0; + int audio_bound = 0; + + vector < MuxStream * >::iterator str; + for (str = streams.begin (); str < streams.end (); ++str) { + switch (((*str)->stream_id & 0xe0)) { + case 0xe0: // MPEG Video + ++video_bound; + break; + case 0xb9: // DVD seems to use this stream id in + ++video_bound; // system headers for video buffer size + break; + case 0xc0: + ++audio_bound; // MPEG Audio + break; + default: + break; + } + } + + /* if we are not using both streams, we should clear some + options here */ + + *(index++) = static_cast < uint8_t > ((SYS_HEADER_START) >> 24); + *(index++) = static_cast < uint8_t > ((SYS_HEADER_START & 0x00ff0000) >> 16); + *(index++) = static_cast < uint8_t > ((SYS_HEADER_START & 0x0000ff00) >> 8); + *(index++) = static_cast < uint8_t > (SYS_HEADER_START & 0x000000ff); + + len_index = index; /* Skip length field for now... */ + index += 2; + + *(index++) = static_cast < uint8_t > (0x80 | (rate_bound >> 15)); + *(index++) = static_cast < uint8_t > (0xff & (rate_bound >> 7)); + *(index++) = static_cast < uint8_t > (0x01 | ((rate_bound & 0x7f) << 1)); + *(index++) = static_cast < uint8_t > ((audio_bound << 2) | (fixed << 1) | CSPS); + *(index++) = static_cast < uint8_t > ((audio_lock << 7) | (video_lock << 6) | 0x20 | video_bound); + + *(index++) = static_cast < uint8_t > (RESERVED_BYTE); + for (str = streams.begin (); str < streams.end (); ++str) { + *(index++) = (*str)->stream_id; + *(index++) = static_cast < uint8_t > + (0xc0 | ((*str)->BufferScale () << 5) | ((*str)->BufferSizeCode () >> 8)); + *(index++) = static_cast < uint8_t > ((*str)->BufferSizeCode () & 0xff); + } + + + system_header_size = (index - sys_header->buf); + len_index[0] = static_cast < uint8_t > ((system_header_size - 6) >> 8); + len_index[1] = static_cast < uint8_t > ((system_header_size - 6) & 0xff); + sys_header->length = system_header_size; +} + + +void +PS_Stream::RawWrite (uint8_t * buf, unsigned int len) +{ + if (callback (this, buf, len, user_data) != len) { + mjpeg_error_exit1 ("Failed write"); + } + written += len; +} + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/systems.hh b/ext/mplex/systems.hh new file mode 100644 index 000000000..bbad92f5c --- /dev/null +++ b/ext/mplex/systems.hh @@ -0,0 +1,131 @@ +#ifndef __SYSTEMS_HH__ +#define __SYSTEMS_HH__ + +#include "inputstrm.hh" + +#include + +using std::vector; + +/* Buffer size parameters */ + +#define MAX_SECTOR_SIZE 16384 +#define MAX_PACK_HEADER_SIZE 255 +#define MAX_SYS_HEADER_SIZE 255 + + +typedef class PS_Stream _PS_Stream; + +typedef size_t (*WriteCallback) (_PS_Stream *str, uint8_t *data, size_t size, void *user_data); + + +typedef struct sector_struc /* Ein Sektor, kann Pack, Sys Header */ +/* und Packet enthalten. */ +{ + unsigned char buf[MAX_SECTOR_SIZE]; + unsigned int length_of_packet_data; + //clockticks TS ; +} +Sector_struc; + +typedef struct pack_struc /* Pack Info */ +{ + unsigned char buf[MAX_PACK_HEADER_SIZE]; + int length; + clockticks SCR; +} +Pack_struc; + +typedef struct sys_header_struc /* System Header Info */ +{ + unsigned char buf[MAX_SYS_HEADER_SIZE]; + int length; +} +Sys_header_struc; + + +class PS_Stream { +public: + PS_Stream (WriteCallback _callback, void *_user_data): + callback (_callback), + user_data (_user_data) + { + } + + void Init (unsigned _mpeg, unsigned int _sector_sizen, off_t max_segment_size); // 0 = No Limit + + bool FileLimReached (); + void NextFile (); + unsigned int PacketPayload (MuxStream & strm, + Sys_header_struc * sys_header, + Pack_struc * pack_header, int buffers, int PTSstamp, int DTSstamp); + + unsigned int CreateSector (Pack_struc * pack, + Sys_header_struc * sys_header, + unsigned int max_packet_data_size, + MuxStream & strm, + bool buffers, bool end_marker, clockticks PTS, clockticks DTS, uint8_t timestamps); + void RawWrite (uint8_t * data, unsigned int len); + static void BufferSectorHeader (uint8_t * buf, + Pack_struc * pack, Sys_header_struc * sys_header, uint8_t * &header_end); + static void BufferPacketHeader (uint8_t * buf, + uint8_t type, + unsigned int mpeg_version, + bool buffers, + unsigned int buffer_size, + uint8_t buffer_scale, + clockticks PTS, + clockticks DTS, + uint8_t timestamps, uint8_t * &size_field, uint8_t * &header_end); + + static inline void BufferPacketSize (uint8_t * size_field, uint8_t * packet_end) + { + unsigned int + packet_size = + packet_end - + size_field - + 2; + + size_field[0] = static_cast < uint8_t > (packet_size >> 8); + size_field[1] = static_cast < uint8_t > (packet_size & 0xff); + + } + + void CreatePack (Pack_struc * pack, clockticks SCR, unsigned int mux_rate); + void CreateSysHeader (Sys_header_struc * sys_header, + unsigned int rate_bound, + bool fixed, + int CSPS, bool audio_lock, bool video_lock, vector < MuxStream * >&streams); + + void Close () + { + } + +private: + + /* TODO: Replace **'s with *&'s */ + static void BufferDtsPtsMpeg1ScrTimecode (clockticks timecode, uint8_t marker, uint8_t ** buffer); + static void BufferMpeg2ScrTimecode (clockticks timecode, uint8_t ** buffer); + void BufferPaddingPacket (int padding, uint8_t * &buffer); + +private: + unsigned int mpeg_version; + unsigned int sector_size; + int segment_num; + + off_t max_segment_size; + uint8_t * sector_buf; + WriteCallback callback; + void *user_data; + off_t written; +}; +#endif // __SYSTEMS_HH__ + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/vector.cc b/ext/mplex/vector.cc new file mode 100644 index 000000000..343b6284f --- /dev/null +++ b/ext/mplex/vector.cc @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include "mjpeg_types.h" +#include "vector.hh" + + +AUStream::AUStream (): +cur_rd (0), cur_wr (0), totalctr (0), size (0), buf (0) +{ + buf = new (Aunit *)[AUStream::BUF_SIZE]; +} + + +void +AUStream::init (Aunit * rec) +{ + buf[cur_wr] = rec; + ++cur_wr; + cur_wr = cur_wr >= AUStream::BUF_SIZE ? 0 : cur_wr; + cur_rd = cur_wr; +} diff --git a/ext/mplex/vector.hh b/ext/mplex/vector.hh new file mode 100644 index 000000000..9a4f04135 --- /dev/null +++ b/ext/mplex/vector.hh @@ -0,0 +1,71 @@ +#ifndef __AUSTREAM_H__ +#define __AUSTREAM_H__ + +#include +#include +#include "mjpeg_logging.h" +#include "aunit.hh" + +class AUStream +{ +public: + AUStream (); + + void init (Aunit * rec); + + void append (Aunit & rec) + { + if (size == BUF_SIZE) + mjpeg_error_exit1 ("INTERNAL ERROR: AU buffer overflow"); + *buf[cur_wr] = rec; + ++size; + ++cur_wr; + cur_wr = cur_wr >= BUF_SIZE ? 0 : cur_wr; + } + + inline Aunit *next () + { + if (size == 0) { + return 0; + } else { + Aunit *ret; + + ret = buf[cur_rd]; + ++cur_rd; + ++totalctr; + --size; + cur_rd = cur_rd >= BUF_SIZE ? 0 : cur_rd; + return ret; + } + } + + inline Aunit *lookahead () + { + return size == 0 ? 0 : buf[cur_rd]; + } + + inline Aunit *last () + { + int i = cur_wr - 1 < 0 ? BUF_SIZE - 1 : cur_wr - 1; + + return buf[i]; + } + + static const unsigned int BUF_SIZE = 128; + + inline unsigned int current () + { + return totalctr; + } +//private: + unsigned int cur_rd; + unsigned int cur_wr; + unsigned int totalctr; + unsigned int size; + Aunit **buf; +}; + + + + +#endif // __AUSTREAM_H__ diff --git a/ext/mplex/videostrm.hh b/ext/mplex/videostrm.hh new file mode 100644 index 000000000..6eeac4761 --- /dev/null +++ b/ext/mplex/videostrm.hh @@ -0,0 +1,155 @@ + +/* + * videostrm.hh: Video stream elementary input stream + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __VIDEOSTRM_H__ +#define __VIDEOSTRM_H__ + +#include "inputstrm.hh" + +class VideoStream:public ElementaryStream +{ +public: + VideoStream (IBitStream & ibs, OutputStream & into); + void Init (const int stream_num); + static bool Probe (IBitStream & bs); + + void Close (); + + inline int AUType () + { + return au->type; + } + + inline bool EndSeq () + { + return au->end_seq; + } + + inline int NextAUType () + { + VAunit *p_au = Lookahead (); + + if (p_au != NULL) + return p_au->type; + else + return NOFRAME; + } + + inline bool SeqHdrNext () + { + VAunit *p_au = Lookahead (); + + return p_au != NULL && p_au->seq_header; + } + + virtual unsigned int NominalBitRate () + { + return bit_rate * 50; + } + + virtual void OutputGOPControlSector (); + bool RunOutComplete (); + virtual bool MuxPossible (clockticks currentSCR); + void SetMaxStdBufferDelay (unsigned int demux_rate); + void OutputSector (); + +protected: + void OutputSeqhdrInfo (); + virtual bool AUBufferNeedsRefill (); + virtual void FillAUbuffer (unsigned int frames_to_buffer); + virtual void InitAUbuffer (); + virtual void NextDTSPTS (clockticks & DTS, clockticks & PTS); + void ScanFirstSeqHeader (); + uint8_t NewAUTimestamps (int AUtype); + bool NewAUBuffers (int AUtype); + +public: + unsigned int num_sequence; + unsigned int num_seq_end; + unsigned int num_pictures; + unsigned int num_groups; + unsigned int num_frames[4]; + unsigned int avg_frames[4]; + + unsigned int horizontal_size; + unsigned int vertical_size; + unsigned int aspect_ratio; + unsigned int picture_rate; + unsigned int bit_rate; + unsigned int comp_bit_rate; + unsigned int peak_bit_rate; + unsigned int vbv_buffer_size; + unsigned int CSPF; + double secs_per_frame; + + + bool dtspts_for_all_au; + bool gop_control_packet; + +protected: + + /* State variables for scanning source bit-stream */ + VAunit access_unit; + unsigned int fields_presented; + unsigned int group_order; + unsigned int group_start_pic; + unsigned int group_start_field; + int temporal_reference; + unsigned int pict_rate; + unsigned int pict_struct; + int pulldown_32; + int repeat_first_field; + int prev_temp_ref; + double frame_rate; + unsigned int max_bits_persec; + int AU_pict_data; + int AU_hdr; + clockticks max_PTS; + clockticks max_STD_buffer_delay; + + int opt_mpeg; + int opt_multifile_segment; +}; + +// +// DVD's generate control sectors for each GOP... +// + +class DVDVideoStream:public VideoStream +{ +public: + DVDVideoStream (IBitStream & ibs, OutputStream & into):VideoStream (ibs, into) + { + gop_control_packet = true; + } + void OutputGOPControlSector (); +}; + +#endif // __INPUTSTRM_H__ + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/videostrm_in.cc b/ext/mplex/videostrm_in.cc new file mode 100644 index 000000000..91e6bb36e --- /dev/null +++ b/ext/mplex/videostrm_in.cc @@ -0,0 +1,429 @@ +/* + * inptstrm.c: Members of video stream class related to raw stream + * scanning and buffering. + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include "videostrm.hh" +#include "outputstream.hh" + + + +static void +marker_bit (IBitStream & bs, unsigned int what) +{ + if (what != bs.get1bit ()) { + mjpeg_error ("Illegal MPEG stream at offset (bits) %lld: supposed marker bit not found.", + bs.bitcount ()); + exit (1); + } +} + + +void +VideoStream::ScanFirstSeqHeader () +{ + if (bs.getbits (32) == SEQUENCE_HEADER) { + num_sequence++; + horizontal_size = bs.getbits (12); + vertical_size = bs.getbits (12); + aspect_ratio = bs.getbits (4); + pict_rate = bs.getbits (4); + picture_rate = pict_rate; + bit_rate = bs.getbits (18); + marker_bit (bs, 1); + vbv_buffer_size = bs.getbits (10); + CSPF = bs.get1bit (); + + } else { + mjpeg_error ("Invalid MPEG Video stream header."); + exit (1); + } + + if (pict_rate > 0 && pict_rate <= mpeg_num_framerates) { + frame_rate = Y4M_RATIO_DBL (mpeg_framerate (pict_rate)); + } else { + frame_rate = 25.0; + } + +} + + + + +void +VideoStream::Init (const int stream_num) +{ + mjpeg_debug ("SETTING video buffer to %d", muxinto.video_buffer_size); + MuxStream::Init (VIDEO_STR_0 + stream_num, 1, // Buffer scale + muxinto.video_buffer_size, 0, // Zero stuffing + muxinto.buffers_in_video, muxinto.always_buffers_in_video); + mjpeg_info ("Scanning for header info: Video stream %02x ", VIDEO_STR_0 + stream_num); + InitAUbuffer (); + + ScanFirstSeqHeader (); + + /* Skip to the end of the 1st AU (*2nd* Picture start!) + */ + AU_hdr = SEQUENCE_HEADER; + AU_pict_data = 0; + AU_start = 0LL; + + OutputSeqhdrInfo (); +} + +// +// Set the Maximum STD buffer delay for this video stream. +// By default we set 1 second but if we have specified a video +// buffer that can hold more than 1.0 seconds demuxed data we +// set the delay to the time to fill the buffer. +// + +void +VideoStream::SetMaxStdBufferDelay (unsigned int dmux_rate) +{ + double max_delay = CLOCKS; + + if (static_cast < double >(BufferSize ()) / dmux_rate > 1.0) + max_delay *= static_cast < double >(BufferSize ()) / dmux_rate; + + // + // To enforce a maximum STD buffer residency the + // calculation is a bit tricky as when we decide to mux we may + // (but not always) have some of the *previous* picture left to + // mux in which case it is the timestamp of the next picture that counts. + // For simplicity we simply reduce the limit by 1.5 frame intervals + // and use the timestamp for the current picture. + // + if (frame_rate > 10.0) + max_STD_buffer_delay = static_cast < clockticks > (max_delay * (frame_rate - 1.5) / frame_rate); + else + max_STD_buffer_delay = static_cast < clockticks > (10.0 * max_delay / frame_rate); + +} + +// +// Return whether AU buffer needs refilling. There are two cases: +// 1. We have less than our look-ahead "FRAME_CHUNK" buffer AU's +// buffered 2. AU's are very small and we could have less than 1 +// sector's worth of data buffered. +// + +bool +VideoStream::AUBufferNeedsRefill () +{ + return + !eoscan + && (aunits.current () + FRAME_CHUNK > last_buffered_AU + || bs.buffered_bytes () < muxinto.sector_size); +} + +// +// Refill the AU unit buffer setting AU PTS DTS from the scanned +// header information... +// + +void +VideoStream::FillAUbuffer (unsigned int frames_to_buffer) +{ + if (eoscan) + return; + + last_buffered_AU += frames_to_buffer; + mjpeg_debug ("Scanning %d video frames to frame %d", frames_to_buffer, last_buffered_AU); + + // We set a limit of 2M to seek before we give up. + // This is intentionally very high because some heavily + // padded still frames may have a loooong gap before + // a following sequence end marker. + while (!bs.eos () && + bs.seek_sync (SYNCWORD_START, 24, 2 * 1024 * 1024) && + decoding_order < last_buffered_AU) + { + syncword = (SYNCWORD_START << 8) + bs.getbits (8); + if (AU_pict_data) { + + /* Handle the header *ending* an AU... + If we have the AU picture data an AU and have now + reached a header marking the end of an AU fill in the + the AU length and append it to the list of AU's and + start a new AU. I.e. sequence and gop headers count as + part of the AU of the corresponding picture + */ + stream_length = bs.bitcount () - 32LL; + switch (syncword) { + case SEQUENCE_HEADER: + case GROUP_START: + case PICTURE_START: + access_unit.start = AU_start; + access_unit.length = (int) (stream_length - AU_start) >> 3; + access_unit.end_seq = 0; + avg_frames[access_unit.type - 1] += access_unit.length; + aunits.append (access_unit); + mjpeg_debug ("Found AU %d: DTS=%d", access_unit.dorder, (int) access_unit.DTS / 300); + AU_hdr = syncword; + AU_start = stream_length; + AU_pict_data = 0; + break; + case SEQUENCE_END: + access_unit.length = ((stream_length - AU_start) >> 3) + 4; + access_unit.end_seq = 1; + aunits.append (access_unit); + mjpeg_info ("Scanned to end AU %d", access_unit.dorder); + avg_frames[access_unit.type - 1] += access_unit.length; + + /* Do we have a sequence split in the video stream? */ + if (!bs.eos () && bs.getbits (32) == SEQUENCE_HEADER) { + stream_length = bs.bitcount () - 32LL; + AU_start = stream_length; + syncword = AU_hdr = SEQUENCE_HEADER; + AU_pict_data = 0; + if (opt_multifile_segment) + mjpeg_warn + ("Sequence end marker found in video stream but single-segment splitting specified!"); + } else { + if (!bs.eos () && !opt_multifile_segment) + mjpeg_warn ("No seq. header starting new sequence after seq. end!"); + } + + num_seq_end++; + break; + } + } + + /* Handle the headers starting an AU... */ + switch (syncword) { + case SEQUENCE_HEADER: + /* TODO: Really we should update the info here so we can handle + streams where parameters change on-the-fly... */ + num_sequence++; + break; + + case GROUP_START: + num_groups++; + group_order = 0; + break; + + case PICTURE_START: + /* We have reached AU's picture data... */ + AU_pict_data = 1; + + prev_temp_ref = temporal_reference; + temporal_reference = bs.getbits (10); + access_unit.type = bs.getbits (3); + + /* Now scan forward a little for an MPEG-2 picture coding extension + so we can get pulldown info (if present) */ + if (bs.seek_sync (EXT_START_CODE, 32, 64) && bs.getbits (4) == CODING_EXT_ID) { + /* Skip: 4 F-codes (4)... */ + (void) bs.getbits (16); + /* Skip: DC Precision(2) */ + (void) bs.getbits (2); + pict_struct = bs.getbits (2); + /* Skip: topfirst (1) frame pred dct (1), + concealment_mv(1), q_scale_type (1), */ + (void) bs.getbits (4); + /* Skip: intra_vlc_format(1), alternate_scan (1) */ + (void) bs.getbits (2); + repeat_first_field = bs.getbits (1); + pulldown_32 |= repeat_first_field; + + } else { + repeat_first_field = 0; + pict_struct = PIC_FRAME; + } + + if (access_unit.type == IFRAME) { + unsigned int bits_persec = + (unsigned int) (((double) (stream_length - prev_offset)) * + 2 * frame_rate / ((double) (2 + fields_presented - group_start_field))); + + if (bits_persec > max_bits_persec) { + max_bits_persec = bits_persec; + } + prev_offset = stream_length; + group_start_pic = decoding_order; + group_start_field = fields_presented; + } + + NextDTSPTS (access_unit.DTS, access_unit.PTS); + + access_unit.dorder = decoding_order; + access_unit.porder = temporal_reference + group_start_pic; + access_unit.seq_header = (AU_hdr == SEQUENCE_HEADER); + + decoding_order++; + group_order++; + + if ((access_unit.type > 0) && (access_unit.type < 5)) { + num_frames[access_unit.type - 1]++; + } + + + if (decoding_order >= old_frames + 1000) { + mjpeg_debug ("Got %d picture headers.", decoding_order); + old_frames = decoding_order; + } + + break; + + + + } + } + last_buffered_AU = decoding_order; + num_pictures = decoding_order; + eoscan = bs.eos (); +} + +void +VideoStream::Close () +{ + + bs.close (); + stream_length = (unsigned int) (AU_start / 8); + for (int i = 0; i < 4; i++) { + avg_frames[i] /= num_frames[i] == 0 ? 1 : num_frames[i]; + } + + comp_bit_rate = (unsigned int) + ((((double) stream_length) / ((double) fields_presented)) * 2.0 + * ((double) frame_rate) + 25.0) / 50; + + /* Peak bit rate in 50B/sec units... */ + peak_bit_rate = ((max_bits_persec / 8) / 50); + mjpeg_info ("VIDEO_STATISTICS: %02x", stream_id); + mjpeg_info ("Video Stream length: %11llu bytes", stream_length / 8); + mjpeg_info ("Sequence headers: %8u", num_sequence); + mjpeg_info ("Sequence ends : %8u", num_seq_end); + mjpeg_info ("No. Pictures : %8u", num_pictures); + mjpeg_info ("No. Groups : %8u", num_groups); + mjpeg_info ("No. I Frames : %8u avg. size%6u bytes", num_frames[0], avg_frames[0]); + mjpeg_info ("No. P Frames : %8u avg. size%6u bytes", num_frames[1], avg_frames[1]); + mjpeg_info ("No. B Frames : %8u avg. size%6u bytes", num_frames[2], avg_frames[2]); + mjpeg_info ("No. D Frames : %8u avg. size%6u bytes", num_frames[3], avg_frames[3]); + mjpeg_info ("Average bit-rate : %8u bits/sec", comp_bit_rate * 400); + mjpeg_info ("Peak bit-rate : %8u bits/sec", peak_bit_rate * 400); + +} + + + + +/************************************************************************* + OutputSeqHdrInfo + Display sequence header parameters +*************************************************************************/ + +void +VideoStream::OutputSeqhdrInfo () +{ + const char *str; + + mjpeg_info ("VIDEO STREAM: %02x", stream_id); + + mjpeg_info ("Frame width : %u", horizontal_size); + mjpeg_info ("Frame height : %u", vertical_size); + if (aspect_ratio <= mpeg_num_aspect_ratios[opt_mpeg - 1]) + str = mpeg_aspect_code_definition (opt_mpeg, aspect_ratio); + else + str = "forbidden"; + mjpeg_info ("Aspect ratio : %s", str); + + + if (picture_rate == 0) + mjpeg_info ("Picture rate : forbidden"); + else if (picture_rate <= mpeg_num_framerates) + mjpeg_info ("Picture rate : %2.3f frames/sec", + Y4M_RATIO_DBL (mpeg_framerate (picture_rate))); + else + mjpeg_info ("Picture rate : %x reserved", picture_rate); + + if (bit_rate == 0x3ffff) { + bit_rate = 0; + mjpeg_info ("Bit rate : variable"); + } else if (bit_rate == 0) + mjpeg_info ("Bit rate : forbidden"); + else + mjpeg_info ("Bit rate : %u bits/sec", bit_rate * 400); + + mjpeg_info ("Vbv buffer size : %u bytes", vbv_buffer_size * 2048); + mjpeg_info ("CSPF : %u", CSPF); +} + +// +// Compute PTS DTS of current AU in the video sequence being +// scanned. This is is the PTS/DTS calculation for normal video only. +// It is virtual and over-ridden for non-standard streams (Stills +// etc!). +// + +void +VideoStream::NextDTSPTS (clockticks & DTS, clockticks & PTS) +{ + if (pict_struct != PIC_FRAME) { + DTS = static_cast < clockticks > (fields_presented * (double) (CLOCKS / 2) / frame_rate); + int dts_fields = temporal_reference * 2 + group_start_field + 1; + + if (temporal_reference == prev_temp_ref) + dts_fields += 1; + PTS = static_cast < clockticks > (dts_fields * (double) (CLOCKS / 2) / frame_rate); + access_unit.porder = temporal_reference + group_start_pic; + fields_presented += 1; + } else if (pulldown_32) { + int frames2field; + int frames3field; + + DTS = static_cast < clockticks > (fields_presented * (double) (CLOCKS / 2) / frame_rate); + if (repeat_first_field) { + frames2field = (temporal_reference + 1) / 2; + frames3field = temporal_reference / 2; + fields_presented += 3; + } else { + frames2field = (temporal_reference) / 2; + frames3field = (temporal_reference + 1) / 2; + fields_presented += 2; + } + PTS = static_cast < clockticks > + ((frames2field * 2 + frames3field * 3 + group_start_field + + 1) * (double) (CLOCKS / 2) / frame_rate); + access_unit.porder = temporal_reference + group_start_pic; + } else { + DTS = static_cast < clockticks > (decoding_order * (double) CLOCKS / frame_rate); + PTS = static_cast < clockticks > + ((temporal_reference + group_start_pic + 1) * (double) CLOCKS / frame_rate); + fields_presented += 2; + } + +} + + + + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/videostrm_out.cc b/ext/mplex/videostrm_out.cc new file mode 100644 index 000000000..a70bd078b --- /dev/null +++ b/ext/mplex/videostrm_out.cc @@ -0,0 +1,276 @@ + +/* + * inptstrm.c: Members of input stream classes related to muxing out into + * the output stream. + * + * Copyright (C) 2001 Andrew Stevens + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include +#include +#include "fastintfns.h" +#include "videostrm.hh" +#include "outputstream.hh" + + +VideoStream::VideoStream (IBitStream & ibs, OutputStream & into): +ElementaryStream (ibs, into, ElementaryStream::video), +num_sequence (0), +num_seq_end (0), +num_pictures (0), +num_groups (0), dtspts_for_all_au (into.dtspts_for_all_vau), gop_control_packet (false) +{ + prev_offset = 0; + decoding_order = 0; + fields_presented = 0; + group_order = 0; + group_start_pic = 0; + group_start_field = 0; + temporal_reference = 0; + pulldown_32 = 0; + temporal_reference = -1; // Needed to recognise 2nd field of 1st + // frame in a field pic sequence + last_buffered_AU = 0; + max_bits_persec = 0; + AU_hdr = SEQUENCE_HEADER; /* GOP or SEQ Header starting AU? */ + for (int i = 0; i < 4; ++i) + num_frames[i] = avg_frames[i] = 0; + FRAME_CHUNK = 6; + + this->opt_mpeg = into.opt_mpeg; + this->opt_multifile_segment = into.opt_multifile_segment; +} + +bool +VideoStream::Probe (IBitStream & bs) +{ + return bs.getbits (32) == 0x1b3; +} + +void +VideoStream::InitAUbuffer () +{ + unsigned int i; + + for (i = 0; i < aunits.BUF_SIZE; ++i) + aunits.init (new VAunit); +} + + +/********************************* + * Signals when video stream has completed mux run-out specified + * in associated mux stream. Run-out is always to complete GOP's. + *********************************/ + +bool +VideoStream::RunOutComplete () +{ + + return (au_unsent == 0 || + (muxinto.running_out && au->type == IFRAME && RequiredPTS () >= muxinto.runout_PTS)); +} + +/********************************* + * Signals if it is permissible/possible to Mux out a sector from the Stream. + * The universal constraints that muxing should not be complete and that + * that the reciever buffer should have sufficient it also insists that + * the muxed data won't hang around in the receiver buffer for more than + * one second. This is mainly for the benefit of (S)VCD and DVD applications + * where long delays mess up random access. + *******************************/ + + +bool +VideoStream::MuxPossible (clockticks currentSCR) +{ + + return (ElementaryStream::MuxPossible (currentSCR) && + RequiredDTS () < currentSCR + max_STD_buffer_delay); +} + +/********************************* + * Work out the timestamps to be set in the header of sectors starting + * new AU's. + *********************************/ + +uint8_t +VideoStream::NewAUTimestamps (int AUtype) +{ + uint8_t timestamps; + + if (AUtype == BFRAME) + timestamps = TIMESTAMPBITS_PTS; + else + timestamps = TIMESTAMPBITS_PTS_DTS; + + if (muxinto.timestamp_iframe_only && AUtype != IFRAME) + timestamps = TIMESTAMPBITS_NO; + return timestamps; +} + +/********************************* + * Work out the buffer records to be set in the header of sectors + * starting new AU's. + *********************************/ + +bool +VideoStream::NewAUBuffers (int AUtype) +{ + return buffers_in_header & !(muxinto.video_buffers_iframe_only && AUtype != IFRAME); +} + +/****************************************************************** + Output_Video + generiert Pack/Sys_Header/Packet Informationen aus dem + Video Stream und speichert den so erhaltenen Sektor ab. + + generates Pack/Sys_Header/Packet information from the + video stream and writes out the new sector +******************************************************************/ + +void +VideoStream::OutputSector () +{ + + unsigned int max_packet_payload; + unsigned int actual_payload; + unsigned int old_au_then_new_payload; + clockticks DTS, PTS; + int autype; + + max_packet_payload = 0; /* 0 = Fill sector */ + /* + We're now in the last AU of a segment. + So we don't want to go beyond it's end when filling + sectors. Hence we limit packet payload size to (remaining) AU length. + The same applies when we wish to ensure sequence headers starting + ACCESS-POINT AU's in (S)VCD's etc are sector-aligned. + */ + int nextAU = NextAUType (); + + if ((muxinto.running_out && nextAU == IFRAME && + NextRequiredPTS () > muxinto.runout_PTS) || + (muxinto.sector_align_iframeAUs && nextAU == IFRAME) + ) { + max_packet_payload = au_unsent; + } + + /* Figure out the threshold payload size below which we can fit more + than one AU into a packet N.b. because fitting more than one in + imposses an overhead of additional header fields so there is a + dead spot where we *have* to stuff the packet rather than start + fitting in an extra AU. Slightly over-conservative in the case + of the last packet... */ + + old_au_then_new_payload = muxinto.PacketPayload (*this, buffers_in_header, true, true); + + /* CASE: Packet starts with new access unit */ + if (new_au_next_sec) { + autype = AUType (); + // + // Some types of output format (e.g. DVD) require special + // control sectors before the sector starting a new GOP + // N.b. this implies muxinto.sector_align_iframeAUs + // + if (gop_control_packet && autype == IFRAME) { + OutputGOPControlSector (); + } + + if (dtspts_for_all_au && max_packet_payload == 0) + max_packet_payload = au_unsent; + + PTS = RequiredPTS (); + DTS = RequiredDTS (); + actual_payload = + muxinto.WritePacket (max_packet_payload, + *this, NewAUBuffers (autype), PTS, DTS, NewAUTimestamps (autype)); + + } + + /* CASE: Packet begins with old access unit, no new one */ + /* can begin in the very same packet */ + + else if (au_unsent >= old_au_then_new_payload || + (max_packet_payload != 0 && au_unsent >= max_packet_payload)) { + actual_payload = muxinto.WritePacket (au_unsent, *this, false, 0, 0, TIMESTAMPBITS_NO); + } + + /* CASE: Packet begins with old access unit, a new one */ + /* could begin in the very same packet */ + else { /* if ( !new_au_next_sec && + (au_unsent < old_au_then_new_payload)) */ + /* Is there a new access unit ? */ + if (Lookahead () != 0) { + autype = NextAUType (); + if (dtspts_for_all_au && max_packet_payload == 0) + max_packet_payload = au_unsent + Lookahead ()->length; + + PTS = NextRequiredPTS (); + DTS = NextRequiredDTS (); + + actual_payload = + muxinto.WritePacket (max_packet_payload, + *this, NewAUBuffers (autype), PTS, DTS, NewAUTimestamps (autype)); + } else { + actual_payload = muxinto.WritePacket (0, *this, false, 0, 0, TIMESTAMPBITS_NO); + } + + } + ++nsec; + buffers_in_header = always_buffers_in_header; +} + + +/*********************************************** + OutputControlSector - Write control sectors prefixing a GOP + For "normal" video streams this doesn't happen and so represents + a bug and triggers an abort. + + In DVD's these sectors carry a system header and what is + presumably indexing and/or sub-title information in + private_stream_2 packets. I have no idea what to put in here so we + simply pad the sector out. +***********************************************/ + +void +VideoStream::OutputGOPControlSector () +{ + abort (); +} + + /****************************************************************** + * OutputGOPControlSector + * DVD System headers are carried in peculiar sectors carrying 2 + * PrivateStream2 packets. We're sticking 0's in the packets + * as we have no idea what's supposed to be in there. + ******************************************************************/ + +void +DVDVideoStream::OutputGOPControlSector () +{ + muxinto.OutputDVDPriv2 (); +} + + +/* + * Local variables: + * c-file-style: "stroustrup" + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/ext/mplex/yuv4mpeg.cc b/ext/mplex/yuv4mpeg.cc new file mode 100644 index 000000000..a9e1aebfb --- /dev/null +++ b/ext/mplex/yuv4mpeg.cc @@ -0,0 +1,880 @@ +/* + * yuv4mpeg.c: Functions for reading and writing "new" YUV4MPEG streams + * + * Copyright (C) 2001 Matthew J. Marjanovic + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include + +#include +#include +#include +#include +#include "yuv4mpeg.h" +#include "yuv4mpeg_intern.h" +#include "mjpeg_logging.h" + + +static int _y4mparam_allow_unknown_tags = 1; /* default is forgiveness */ + +static void *(*_y4m_alloc) (size_t bytes) = malloc; +static void (*_y4m_free) (void *ptr) = free; + + + +int +y4m_allow_unknown_tags (int yn) +{ + int old = _y4mparam_allow_unknown_tags; + + if (yn >= 0) + _y4mparam_allow_unknown_tags = (yn) ? 1 : 0; + return old; +} + + + +/************************************************************************* + * + * Convenience functions for fd read/write + * + * - guaranteed to transfer entire payload (or fail) + * - returns: + * 0 on complete success + * +(# of remaining bytes) on eof (for y4m_read) + * -(# of rem. bytes) on error (and ERRNO should be set) + * + *************************************************************************/ + + +ssize_t +y4m_read (int fd, void *buf, size_t len) +{ + ssize_t n; + uint8_t *ptr = (uint8_t *) buf; + + while (len > 0) { + n = read (fd, ptr, len); + if (n <= 0) { + /* return amount left to read */ + if (n == 0) + return len; /* n == 0 --> eof */ + else + return -len; /* n < 0 --> error */ + } + ptr += n; + len -= n; + } + return 0; +} + + +ssize_t +y4m_write (int fd, const void *buf, size_t len) +{ + ssize_t n; + const uint8_t *ptr = (const uint8_t *) buf; + + while (len > 0) { + n = write (fd, ptr, len); + if (n <= 0) + return -len; /* return amount left to write */ + ptr += n; + len -= n; + } + return 0; +} + + + +/************************************************************************* + * + * "Extra tags" handling + * + *************************************************************************/ + + +static char * +y4m_new_xtag (void) +{ + return (char *) _y4m_alloc (Y4M_MAX_XTAG_SIZE * sizeof (char)); +} + + +void +y4m_init_xtag_list (y4m_xtag_list_t * xtags) +{ + int i; + + xtags->count = 0; + for (i = 0; i < Y4M_MAX_XTAGS; i++) { + xtags->tags[i] = NULL; + } +} + + +void +y4m_fini_xtag_list (y4m_xtag_list_t * xtags) +{ + int i; + + for (i = 0; i < Y4M_MAX_XTAGS; i++) { + if (xtags->tags[i] != NULL) { + _y4m_free (xtags->tags[i]); + xtags->tags[i] = NULL; + } + } + xtags->count = 0; +} + + +void +y4m_copy_xtag_list (y4m_xtag_list_t * dest, const y4m_xtag_list_t * src) +{ + int i; + + for (i = 0; i < src->count; i++) { + if (dest->tags[i] == NULL) + dest->tags[i] = y4m_new_xtag (); + strncpy (dest->tags[i], src->tags[i], Y4M_MAX_XTAG_SIZE); + } + dest->count = src->count; +} + + + +static int +y4m_snprint_xtags (char *s, int maxn, const y4m_xtag_list_t * xtags) +{ + int i, room; + + for (i = 0, room = maxn - 1; i < xtags->count; i++) { + int n = snprintf (s, room + 1, " %s", xtags->tags[i]); + + if ((n < 0) || (n > room)) + return Y4M_ERR_HEADER; + s += n; + room -= n; + } + s[0] = '\n'; /* finish off header with newline */ + s[1] = '\0'; /* ...and end-of-string */ + return Y4M_OK; +} + + +int +y4m_xtag_count (const y4m_xtag_list_t * xtags) +{ + return xtags->count; +} + + +const char * +y4m_xtag_get (const y4m_xtag_list_t * xtags, int n) +{ + if (n >= xtags->count) + return NULL; + else + return xtags->tags[n]; +} + + +int +y4m_xtag_add (y4m_xtag_list_t * xtags, const char *tag) +{ + if (xtags->count >= Y4M_MAX_XTAGS) + return Y4M_ERR_XXTAGS; + if (xtags->tags[xtags->count] == NULL) + xtags->tags[xtags->count] = y4m_new_xtag (); + strncpy (xtags->tags[xtags->count], tag, Y4M_MAX_XTAG_SIZE); + (xtags->count)++; + return Y4M_OK; +} + + +int +y4m_xtag_remove (y4m_xtag_list_t * xtags, int n) +{ + int i; + char *q; + + if ((n < 0) || (n >= xtags->count)) + return Y4M_ERR_RANGE; + q = xtags->tags[n]; + for (i = n; i < (xtags->count - 1); i++) + xtags->tags[i] = xtags->tags[i + 1]; + xtags->tags[i] = q; + (xtags->count)--; + return Y4M_OK; +} + + +int +y4m_xtag_clearlist (y4m_xtag_list_t * xtags) +{ + xtags->count = 0; + return Y4M_OK; +} + + +int +y4m_xtag_addlist (y4m_xtag_list_t * dest, const y4m_xtag_list_t * src) +{ + int i, j; + + if ((dest->count + src->count) > Y4M_MAX_XTAGS) + return Y4M_ERR_XXTAGS; + for (i = dest->count, j = 0; j < src->count; i++, j++) { + if (dest->tags[i] == NULL) + dest->tags[i] = y4m_new_xtag (); + strncpy (dest->tags[i], src->tags[i], Y4M_MAX_XTAG_SIZE); + } + dest->count += src->count; + return Y4M_OK; +} + + +/************************************************************************* + * + * Creators/destructors for y4m_*_info_t structures + * + *************************************************************************/ + + +void +y4m_init_stream_info (y4m_stream_info_t * info) +{ + if (info == NULL) + return; + /* initialize info */ + info->width = Y4M_UNKNOWN; + info->height = Y4M_UNKNOWN; + info->interlace = Y4M_UNKNOWN; + info->framerate = y4m_fps_UNKNOWN; + info->sampleaspect = y4m_sar_UNKNOWN; + y4m_init_xtag_list (&(info->x_tags)); +} + + +void +y4m_copy_stream_info (y4m_stream_info_t * dest, const y4m_stream_info_t * src) +{ + if ((dest == NULL) || (src == NULL)) + return; + /* copy info */ + dest->width = src->width; + dest->height = src->height; + dest->interlace = src->interlace; + dest->framerate = src->framerate; + dest->sampleaspect = src->sampleaspect; + dest->framelength = src->framelength; + y4m_copy_xtag_list (&(dest->x_tags), &(src->x_tags)); +} + + +void +y4m_fini_stream_info (y4m_stream_info_t * info) +{ + if (info == NULL) + return; + y4m_fini_xtag_list (&(info->x_tags)); +} + + +void +y4m_si_set_width (y4m_stream_info_t * si, int width) +{ + si->width = width; + si->framelength = (si->height * si->width) * 3 / 2; +} + +int +y4m_si_get_width (const y4m_stream_info_t * si) +{ + return si->width; +} + +void +y4m_si_set_height (y4m_stream_info_t * si, int height) +{ + si->height = height; + si->framelength = (si->height * si->width) * 3 / 2; +} + +int +y4m_si_get_height (const y4m_stream_info_t * si) +{ + return si->height; +} + +void +y4m_si_set_interlace (y4m_stream_info_t * si, int interlace) +{ + si->interlace = interlace; +} + +int +y4m_si_get_interlace (const y4m_stream_info_t * si) +{ + return si->interlace; +} + +void +y4m_si_set_framerate (y4m_stream_info_t * si, y4m_ratio_t framerate) +{ + si->framerate = framerate; +} + +y4m_ratio_t +y4m_si_get_framerate (const y4m_stream_info_t * si) +{ + return si->framerate; +} + +void +y4m_si_set_sampleaspect (y4m_stream_info_t * si, y4m_ratio_t sar) +{ + si->sampleaspect = sar; +} + +y4m_ratio_t +y4m_si_get_sampleaspect (const y4m_stream_info_t * si) +{ + return si->sampleaspect; +} + +int +y4m_si_get_framelength (const y4m_stream_info_t * si) +{ + return si->framelength; +} + +y4m_xtag_list_t * +y4m_si_xtags (y4m_stream_info_t * si) +{ + return &(si->x_tags); +} + + + +void +y4m_init_frame_info (y4m_frame_info_t * info) +{ + if (info == NULL) + return; + /* initialize info */ + y4m_init_xtag_list (&(info->x_tags)); +} + + +void +y4m_copy_frame_info (y4m_frame_info_t * dest, const y4m_frame_info_t * src) +{ + if ((dest == NULL) || (src == NULL)) + return; + /* copy info */ + y4m_copy_xtag_list (&(dest->x_tags), &(src->x_tags)); +} + + +void +y4m_fini_frame_info (y4m_frame_info_t * info) +{ + if (info == NULL) + return; + y4m_fini_xtag_list (&(info->x_tags)); +} + + + +/************************************************************************* + * + * Tag parsing + * + *************************************************************************/ + +int +y4m_parse_stream_tags (char *s, y4m_stream_info_t * i) +{ + char *token, *value; + char tag; + int err; + + /* parse fields */ + for (token = strtok (s, Y4M_DELIM); token != NULL; token = strtok (NULL, Y4M_DELIM)) { + if (token[0] == '\0') + continue; /* skip empty strings */ + tag = token[0]; + value = token + 1; + switch (tag) { + case 'W': /* width */ + i->width = atoi (value); + if (i->width <= 0) + return Y4M_ERR_RANGE; + break; + case 'H': /* height */ + i->height = atoi (value); + if (i->height <= 0) + return Y4M_ERR_RANGE; + break; + case 'F': /* frame rate (fps) */ + if ((err = y4m_parse_ratio (&(i->framerate), value)) != Y4M_OK) + return err; + if (i->framerate.n < 0) + return Y4M_ERR_RANGE; + break; + case 'I': /* interlacing */ + switch (value[0]) { + case 'p': + i->interlace = Y4M_ILACE_NONE; + break; + case 't': + i->interlace = Y4M_ILACE_TOP_FIRST; + break; + case 'b': + i->interlace = Y4M_ILACE_BOTTOM_FIRST; + break; + case '?': + default: + i->interlace = Y4M_UNKNOWN; + break; + } + break; + case 'A': /* sample (pixel) aspect ratio */ + if ((err = y4m_parse_ratio (&(i->sampleaspect), value)) != Y4M_OK) + return err; + if (i->sampleaspect.n < 0) + return Y4M_ERR_RANGE; + break; + case 'X': /* 'X' meta-tag */ + if ((err = y4m_xtag_add (&(i->x_tags), token)) != Y4M_OK) + return err; + break; + default: + /* possible error on unknown options */ + if (_y4mparam_allow_unknown_tags) { + /* unknown tags ok: store in xtag list and warn... */ + if ((err = y4m_xtag_add (&(i->x_tags), token)) != Y4M_OK) + return err; + mjpeg_warn ("Unknown stream tag encountered: '%s'", token); + } else { + /* unknown tags are *not* ok */ + return Y4M_ERR_BADTAG; + } + break; + } + } + /* Error checking... width and height must be known since we can't + * parse without them + */ + if (i->width == Y4M_UNKNOWN || i->height == Y4M_UNKNOWN) + return Y4M_ERR_HEADER; + /* ta da! done. */ + return Y4M_OK; +} + + + +static int +y4m_parse_frame_tags (char *s, y4m_frame_info_t * i) +{ + char *token, *value; + char tag; + int err; + + /* parse fields */ + for (token = strtok (s, Y4M_DELIM); token != NULL; token = strtok (NULL, Y4M_DELIM)) { + if (token[0] == '\0') + continue; /* skip empty strings */ + tag = token[0]; + value = token + 1; + switch (tag) { + case 'X': /* 'X' meta-tag */ + if ((err = y4m_xtag_add (&(i->x_tags), token)) != Y4M_OK) + return err; + break; + default: + /* possible error on unknown options */ + if (_y4mparam_allow_unknown_tags) { + /* unknown tags ok: store in xtag list and warn... */ + if ((err = y4m_xtag_add (&(i->x_tags), token)) != Y4M_OK) + return err; + mjpeg_warn ("Unknown frame tag encountered: '%s'", token); + } else { + /* unknown tags are *not* ok */ + return Y4M_ERR_BADTAG; + } + break; + } + } + /* ta da! done. */ + return Y4M_OK; +} + + + + + +/************************************************************************* + * + * Read/Write stream header + * + *************************************************************************/ + + +int +y4m_read_stream_header (int fd, y4m_stream_info_t * i) +{ + char line[Y4M_LINE_MAX]; + char *p; + int n; + int err; + + /* read the header line */ + for (n = 0, p = line; n < Y4M_LINE_MAX; n++, p++) { + if (read (fd, p, 1) < 1) + return Y4M_ERR_SYSTEM; + if (*p == '\n') { + *p = '\0'; /* Replace linefeed by end of string */ + break; + } + } + if (n >= Y4M_LINE_MAX) + return Y4M_ERR_HEADER; + /* look for keyword in header */ + if (strncmp (line, Y4M_MAGIC, strlen (Y4M_MAGIC))) + return Y4M_ERR_MAGIC; + if ((err = y4m_parse_stream_tags (line + strlen (Y4M_MAGIC), i)) != Y4M_OK) + return err; + + i->framelength = (i->height * i->width) * 3 / 2; + return Y4M_OK; +} + + + +int +y4m_write_stream_header (int fd, const y4m_stream_info_t * i) +{ + char s[Y4M_LINE_MAX + 1]; + int n; + int err; + y4m_ratio_t rate = i->framerate; + y4m_ratio_t aspect = i->sampleaspect; + + y4m_ratio_reduce (&rate); + y4m_ratio_reduce (&aspect); + n = snprintf (s, sizeof (s), "%s W%d H%d F%d:%d I%s A%d:%d", + Y4M_MAGIC, + i->width, + i->height, + rate.n, rate.d, + (i->interlace == Y4M_ILACE_NONE) ? "p" : + (i->interlace == Y4M_ILACE_TOP_FIRST) ? "t" : + (i->interlace == Y4M_ILACE_BOTTOM_FIRST) ? "b" : "?", aspect.n, aspect.d); + if ((n < 0) || (n > Y4M_LINE_MAX)) + return Y4M_ERR_HEADER; + if ((err = y4m_snprint_xtags (s + n, sizeof (s) - n - 1, &(i->x_tags))) + != Y4M_OK) + return err; + /* non-zero on error */ + return (y4m_write (fd, s, strlen (s)) ? Y4M_ERR_SYSTEM : Y4M_OK); +} + + + + + +/************************************************************************* + * + * Read/Write frame header + * + *************************************************************************/ + +int +y4m_read_frame_header (int fd, y4m_frame_info_t * i) +{ + char line[Y4M_LINE_MAX]; + char *p; + int n; + ssize_t remain; + + /* This is more clever than read_stream_header... + Try to read "FRAME\n" all at once, and don't try to parse + if nothing else is there... + */ + remain = y4m_read (fd, line, sizeof (Y4M_FRAME_MAGIC) - 1 + 1); /* -'\0', +'\n' */ + if (remain < 0) + return Y4M_ERR_SYSTEM; + if (remain > 0) { + /* A clean EOF should end exactly at a frame-boundary */ + if (remain == sizeof (Y4M_FRAME_MAGIC)) + return Y4M_ERR_EOF; + else + return Y4M_ERR_BADEOF; + } + if (strncmp (line, Y4M_FRAME_MAGIC, sizeof (Y4M_FRAME_MAGIC) - 1)) + return Y4M_ERR_MAGIC; + if (line[sizeof (Y4M_FRAME_MAGIC) - 1] == '\n') + return Y4M_OK; /* done -- no tags: that was the end-of-line. */ + + if (line[sizeof (Y4M_FRAME_MAGIC) - 1] != Y4M_DELIM[0]) { + return Y4M_ERR_MAGIC; /* wasn't a space -- what was it? */ + } + + /* proceed to get the tags... (overwrite the magic) */ + for (n = 0, p = line; n < Y4M_LINE_MAX; n++, p++) { + if (y4m_read (fd, p, 1)) + return Y4M_ERR_SYSTEM; + if (*p == '\n') { + *p = '\0'; /* Replace linefeed by end of string */ + break; + } + } + if (n >= Y4M_LINE_MAX) + return Y4M_ERR_HEADER; + /* non-zero on error */ + return y4m_parse_frame_tags (line, i); +} + + +int +y4m_write_frame_header (int fd, const y4m_frame_info_t * i) +{ + char s[Y4M_LINE_MAX + 1]; + int n; + int err; + + n = snprintf (s, sizeof (s), "%s", Y4M_FRAME_MAGIC); + if ((n < 0) || (n > Y4M_LINE_MAX)) + return Y4M_ERR_HEADER; + if ((err = y4m_snprint_xtags (s + n, sizeof (s) - n - 1, &(i->x_tags))) + != Y4M_OK) + return err; + /* non-zero on error */ + return (y4m_write (fd, s, strlen (s)) ? Y4M_ERR_SYSTEM : Y4M_OK); +} + + + +/************************************************************************* + * + * Read/Write entire frame + * + *************************************************************************/ + +int +y4m_read_frame (int fd, const y4m_stream_info_t * si, y4m_frame_info_t * fi, uint8_t * const yuv[3]) +{ + int err; + int w = si->width; + int h = si->height; + + /* Read frame header */ + if ((err = y4m_read_frame_header (fd, fi)) != Y4M_OK) + return err; + /* Read luminance scanlines */ + if (y4m_read (fd, yuv[0], w * h)) + return Y4M_ERR_SYSTEM; + /* Read chrominance scanlines */ + if (y4m_read (fd, yuv[1], w * h / 4)) + return Y4M_ERR_SYSTEM; + if (y4m_read (fd, yuv[2], w * h / 4)) + return Y4M_ERR_SYSTEM; + + return Y4M_OK; +} + + + + +int +y4m_write_frame (int fd, const y4m_stream_info_t * si, + const y4m_frame_info_t * fi, uint8_t * const yuv[3]) +{ + int err; + int w = si->width; + int h = si->height; + + /* Write frame header */ + if ((err = y4m_write_frame_header (fd, fi)) != Y4M_OK) + return err; + /* Write luminance,chrominance scanlines */ + if (y4m_write (fd, yuv[0], w * h) || + y4m_write (fd, yuv[1], w * h / 4) || y4m_write (fd, yuv[2], w * h / 4)) + return Y4M_ERR_SYSTEM; + return Y4M_OK; +} + + + +/************************************************************************* + * + * Read/Write entire frame, (de)interleaved (to)from two separate fields + * + *************************************************************************/ + + +int +y4m_read_fields (int fd, const y4m_stream_info_t * si, y4m_frame_info_t * fi, + uint8_t * const upper_field[3], uint8_t * const lower_field[3]) +{ + int i, y, err; + int width = si->width; + int height = si->height; + + /* Read frame header */ + if ((err = y4m_read_frame_header (fd, fi)) != Y4M_OK) + return err; + /* Read Y', Cb, and Cr planes */ + for (i = 0; i < 3; i++) { + uint8_t *srctop = upper_field[i]; + uint8_t *srcbot = lower_field[i]; + + /* alternately write one line from each */ + for (y = 0; y < height; y += 2) { + if (y4m_read (fd, srctop, width)) + return Y4M_ERR_SYSTEM; + srctop += width; + if (y4m_read (fd, srcbot, width)) + return Y4M_ERR_SYSTEM; + srcbot += width; + } + /* for chroma, width/height are half as big */ + if (i == 0) { + width /= 2; + height /= 2; + } + } + return Y4M_OK; +} + + + +int +y4m_write_fields (int fd, const y4m_stream_info_t * si, + const y4m_frame_info_t * fi, + uint8_t * const upper_field[3], uint8_t * const lower_field[3]) +{ + int i, y, err; + int width = si->width; + int height = si->height; + + /* Write frame header */ + if ((err = y4m_write_frame_header (fd, fi)) != Y4M_OK) + return err; + /* Write Y', Cb, and Cr planes */ + for (i = 0; i < 3; i++) { + uint8_t *srctop = upper_field[i]; + uint8_t *srcbot = lower_field[i]; + + /* alternately write one line from each */ + for (y = 0; y < height; y += 2) { + if (y4m_write (fd, srctop, width)) + return Y4M_ERR_SYSTEM; + srctop += width; + if (y4m_write (fd, srcbot, width)) + return Y4M_ERR_SYSTEM; + srcbot += width; + } + /* for chroma, width/height are half as big */ + if (i == 0) { + width /= 2; + height /= 2; + } + } + return Y4M_OK; +} + + + +/************************************************************************* + * + * Handy logging of stream info + * + *************************************************************************/ + +void +y4m_log_stream_info (log_level_t level, const char *prefix, const y4m_stream_info_t * i) +{ + char s[256]; + + snprintf (s, sizeof (s), " frame size: "); + if (i->width == Y4M_UNKNOWN) + snprintf (s + strlen (s), sizeof (s) - strlen (s), "(?)x"); + else + snprintf (s + strlen (s), sizeof (s) - strlen (s), "%dx", i->width); + if (i->height == Y4M_UNKNOWN) + snprintf (s + strlen (s), sizeof (s) - strlen (s), "(?) pixels "); + else + snprintf (s + strlen (s), sizeof (s) - strlen (s), "%d pixels ", i->height); + if (i->framelength == Y4M_UNKNOWN) + snprintf (s + strlen (s), sizeof (s) - strlen (s), "(? bytes)"); + else + snprintf (s + strlen (s), sizeof (s) - strlen (s), "(%d bytes)", i->framelength); + mjpeg_log (level, "%s%s", prefix, s); + if ((i->framerate.n == 0) && (i->framerate.d == 0)) + mjpeg_log (level, "%s frame rate: ??? fps", prefix); + else + mjpeg_log (level, "%s frame rate: %d/%d fps (~%f)", prefix, + i->framerate.n, i->framerate.d, (double) i->framerate.n / (double) i->framerate.d); + mjpeg_log (level, "%s interlace: %s", prefix, + (i->interlace == Y4M_ILACE_NONE) ? "none/progressive" : + (i->interlace == Y4M_ILACE_TOP_FIRST) ? "top-field-first" : + (i->interlace == Y4M_ILACE_BOTTOM_FIRST) ? "bottom-field-first" : "anyone's guess"); + if ((i->sampleaspect.n == 0) && (i->sampleaspect.d == 0)) + mjpeg_log (level, "%ssample aspect ratio: ?:?", prefix); + else + mjpeg_log (level, "%ssample aspect ratio: %d:%d", prefix, + i->sampleaspect.n, i->sampleaspect.d); +} + + +/************************************************************************* + * + * Convert error code to string + * + *************************************************************************/ + +const char * +y4m_strerr (int err) +{ + switch (err) { + case Y4M_OK: + return "no error"; + case Y4M_ERR_RANGE: + return "parameter out of range"; + case Y4M_ERR_SYSTEM: + return "system error (failed read/write)"; + case Y4M_ERR_HEADER: + return "bad stream or frame header"; + case Y4M_ERR_BADTAG: + return "unknown header tag"; + case Y4M_ERR_MAGIC: + return "bad header magic"; + case Y4M_ERR_XXTAGS: + return "too many xtags"; + case Y4M_ERR_EOF: + return "end-of-file"; + case Y4M_ERR_BADEOF: + return "stream ended unexpectedly (EOF)"; + default: + return "unknown error code"; + } +} diff --git a/ext/mplex/yuv4mpeg.h b/ext/mplex/yuv4mpeg.h new file mode 100644 index 000000000..6028ea885 --- /dev/null +++ b/ext/mplex/yuv4mpeg.h @@ -0,0 +1,473 @@ +/* + * yuv4mpeg.h: Functions for reading and writing "new" YUV4MPEG2 streams. + * + * Stream format is described at the end of this file. + * + * + * Copyright (C) 2001 Matthew J. Marjanovic + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __YUV4MPEG_H__ +#define __YUV4MPEG_H__ + +#include +#include +#include +#include + + +/************************************************************************ + * error codes returned by y4m_* functions + ************************************************************************/ +#define Y4M_OK 0 +#define Y4M_ERR_RANGE 1 /* argument or tag value out of range */ +#define Y4M_ERR_SYSTEM 2 /* failed system call, check errno */ +#define Y4M_ERR_HEADER 3 /* illegal/malformed header */ +#define Y4M_ERR_BADTAG 4 /* illegal tag character */ +#define Y4M_ERR_MAGIC 5 /* bad header magic */ +#define Y4M_ERR_EOF 6 /* end-of-file (clean) */ +#define Y4M_ERR_XXTAGS 7 /* too many xtags */ +#define Y4M_ERR_BADEOF 8 /* unexpected end-of-file */ + + +/* generic 'unknown' value for integer parameters (e.g. interlace, height) */ +#define Y4M_UNKNOWN -1 + + + +/************************************************************************ + * 'ratio' datatype, for rational numbers + * (see 'ratio' functions down below) + ************************************************************************/ +typedef struct _y4m_ratio { + int n; /* numerator */ + int d; /* denominator */ +} y4m_ratio_t; + + +/************************************************************************ + * useful standard framerates (as ratios) + ************************************************************************/ +extern const y4m_ratio_t y4m_fps_UNKNOWN; +extern const y4m_ratio_t y4m_fps_NTSC_FILM; /* 24000/1001 film (in NTSC) */ +extern const y4m_ratio_t y4m_fps_FILM; /* 24fps film */ +extern const y4m_ratio_t y4m_fps_PAL; /* 25fps PAL */ +extern const y4m_ratio_t y4m_fps_NTSC; /* 30000/1001 NTSC */ +extern const y4m_ratio_t y4m_fps_30; /* 30fps */ +extern const y4m_ratio_t y4m_fps_PAL_FIELD; /* 50fps PAL field rate */ +extern const y4m_ratio_t y4m_fps_NTSC_FIELD; /* 60000/1001 NTSC field rate */ +extern const y4m_ratio_t y4m_fps_60; /* 60fps */ + +/************************************************************************ + * useful standard sample (pixel) aspect ratios (W:H) + ************************************************************************/ +extern const y4m_ratio_t y4m_sar_UNKNOWN; +extern const y4m_ratio_t y4m_sar_SQUARE; /* square pixels */ +extern const y4m_ratio_t y4m_sar_NTSC_CCIR601; /* 525-line (NTSC) Rec.601 */ +extern const y4m_ratio_t y4m_sar_NTSC_16_9; /* 16:9 NTSC/Rec.601 */ +extern const y4m_ratio_t y4m_sar_NTSC_SVCD_4_3; /* NTSC SVCD 4:3 */ +extern const y4m_ratio_t y4m_sar_NTSC_SVCD_16_9;/* NTSC SVCD 16:9 */ +extern const y4m_ratio_t y4m_sar_PAL_CCIR601; /* 625-line (PAL) Rec.601 */ +extern const y4m_ratio_t y4m_sar_PAL_16_9; /* 16:9 PAL/Rec.601 */ +extern const y4m_ratio_t y4m_sar_PAL_SVCD_4_3; /* PAL SVCD 4:3 */ +extern const y4m_ratio_t y4m_sar_PAL_SVCD_16_9; /* PAL SVCD 16:9 */ +extern const y4m_ratio_t y4m_sar_SQR_ANA16_9; /* anamorphic 16:9 sampled */ + /* from 4:3 with square pixels */ + +/************************************************************************ + * useful standard display aspect ratios (W:H) + ************************************************************************/ +extern const y4m_ratio_t y4m_dar_UNKNOWN; +extern const y4m_ratio_t y4m_dar_4_3; /* standard TV */ +extern const y4m_ratio_t y4m_dar_16_9; /* widescreen TV */ +extern const y4m_ratio_t y4m_dar_221_100; /* word-to-your-mother TV */ + + +/************************************************************************ + * 'xtag_list' --- list of unparsed and/or meta/X header tags + * + * Do not touch this structure directly! + * + * Use the y4m_xtag_*() functions (see below). + * You must initialize/finalize this structure before/after use. + ************************************************************************/ +#define Y4M_MAX_XTAGS 32 /* maximum number of xtags in list */ +#define Y4M_MAX_XTAG_SIZE 32 /* max length of an xtag (including 'X') */ +typedef struct _y4m_xtag_list { + int count; + char *tags[Y4M_MAX_XTAGS]; +} y4m_xtag_list_t; + + + +/************************************************************************ + * 'stream_info' --- stream header information + * + * Do not touch this structure directly! + * + * Use the y4m_si_*() functions (see below). + * You must initialize/finalize this structure before/after use. + ************************************************************************/ +typedef struct _y4m_stream_info { + /* values from header */ + int width; + int height; + int interlace; /* see Y4M_ILACE_* definitions below */ + y4m_ratio_t framerate; /* frames-per-second; 0:0 == unknown */ + y4m_ratio_t sampleaspect; /* pixel width/height; 0:0 == unknown */ + /* computed/derivative values */ + int framelength; /* bytes of data per frame (not including header) */ + /* mystical X tags */ + y4m_xtag_list_t x_tags; +} y4m_stream_info_t; + +/* possible options for the interlace parameter */ +#define Y4M_ILACE_NONE 0 /* non-interlaced, progressive frame */ +#define Y4M_ILACE_TOP_FIRST 1 /* interlaced, top-field first */ +#define Y4M_ILACE_BOTTOM_FIRST 2 /* interlaced, bottom-field first */ + + +/************************************************************************ + * 'frame_info' --- frame header information + * + * Do not touch this structure directly! + * + * Use the y4m_fi_*() functions (see below). + * You must initialize/finalize this structure before/after use. + ************************************************************************/ +typedef struct _y4m_frame_info { + /* mystical X tags */ + y4m_xtag_list_t x_tags; +} y4m_frame_info_t; + + + +#ifdef __cplusplus +extern "C" { +#else +#endif + + +/************************************************************************ + * 'ratio' functions + ************************************************************************/ + +/* 'normalize' a ratio (remove common factors) */ +void y4m_ratio_reduce(y4m_ratio_t *r); + +/* parse "nnn:ddd" into a ratio (returns Y4M_OK or Y4M_ERR_RANGE) */ +int y4m_parse_ratio(y4m_ratio_t *r, const char *s); + +/* quick test of two ratios for equality (i.e. identical components) */ +#define Y4M_RATIO_EQL(a,b) ( ((a).n == (b).n) && ((a).d == (b).d) ) + +/* quick conversion of a ratio to a double (no divide-by-zero check!) */ +#define Y4M_RATIO_DBL(r) ((double)(r).n / (double)(r).d) + +/************************************************************************* + * + * Guess the true SAR (sample aspect ratio) from a list of commonly + * encountered values, given the "suggested" display aspect ratio (DAR), + * and the true frame width and height. + * + * Returns y4m_sar_UNKNOWN if no match is found. + * + *************************************************************************/ +y4m_ratio_t y4m_guess_sar(int width, int height, y4m_ratio_t dar); + + + +/************************************************************************ + * 'xtag' functions + * + * o Before using an xtag_list (but after the structure/memory has been + * allocated), you must initialize it via y4m_init_xtag_list(). + * o After using an xtag_list (but before the structure is released), + * call y4m_fini_xtag_list() to free internal memory. + * + ************************************************************************/ + +/* initialize an xtag_list structure */ +void y4m_init_xtag_list(y4m_xtag_list_t *xtags); + +/* finalize an xtag_list structure */ +void y4m_fini_xtag_list(y4m_xtag_list_t *xtags); + +/* make one xtag_list into a copy of another */ +void y4m_copy_xtag_list(y4m_xtag_list_t *dest, const y4m_xtag_list_t *src); + +/* return number of tags in an xtag_list */ +int y4m_xtag_count(const y4m_xtag_list_t *xtags); + +/* access n'th tag in an xtag_list */ +const char *y4m_xtag_get(const y4m_xtag_list_t *xtags, int n); + +/* append a new tag to an xtag_list + returns: Y4M_OK - success + Y4M_ERR_XXTAGS - list is already full */ +int y4m_xtag_add(y4m_xtag_list_t *xtags, const char *tag); + +/* remove a tag from an xtag_list + returns: Y4M_OK - success + Y4M_ERR_RANGE - n is out of range */ +int y4m_xtag_remove(y4m_xtag_list_t *xtags, int n); + +/* remove all tags from an xtag_list + returns: Y4M_OK - success */ +int y4m_xtag_clearlist(y4m_xtag_list_t *xtags); + +/* append copies of tags from src list to dest list + returns: Y4M_OK - success + Y4M_ERR_XXTAGS - operation would overfill dest list */ +int y4m_xtag_addlist(y4m_xtag_list_t *dest, const y4m_xtag_list_t *src); + + + +/************************************************************************ + * '*_info' functions + * + * o Before using a *_info structure (but after the structure/memory has + * been allocated), you must initialize it via y4m_init_*_info(). + * o After using a *_info structure (but before the structure is released), + * call y4m_fini_*_info() to free internal memory. + * o Use the 'set' and 'get' accessors to modify or access the fields in + * the structures; don't touch the structure directly. (Ok, so there + * is no really convenient C syntax to prevent you from doing this, + * but we are all responsible programmers here, so just don't do it!) + * + ************************************************************************/ + +/* initialize a stream_info structure */ +void y4m_init_stream_info(y4m_stream_info_t *i); + +/* finalize a stream_info structure */ +void y4m_fini_stream_info(y4m_stream_info_t *i); + +/* make one stream_info into a copy of another */ +void y4m_copy_stream_info(y4m_stream_info_t *dest, + const y4m_stream_info_t *src); + +/* access or set stream_info fields */ +void y4m_si_set_width(y4m_stream_info_t *si, int width); +int y4m_si_get_width(const y4m_stream_info_t *si); +void y4m_si_set_height(y4m_stream_info_t *si, int height); +int y4m_si_get_height(const y4m_stream_info_t *si); +void y4m_si_set_interlace(y4m_stream_info_t *si, int interlace); +int y4m_si_get_interlace(const y4m_stream_info_t *si); +void y4m_si_set_framerate(y4m_stream_info_t *si, y4m_ratio_t framerate); +y4m_ratio_t y4m_si_get_framerate(const y4m_stream_info_t *si); +void y4m_si_set_sampleaspect(y4m_stream_info_t *si, y4m_ratio_t sar); +y4m_ratio_t y4m_si_get_sampleaspect(const y4m_stream_info_t *si); +int y4m_si_get_framelength(const y4m_stream_info_t *si); + +/* access stream_info xtag_list */ +y4m_xtag_list_t *y4m_si_xtags(y4m_stream_info_t *si); + + +/* initialize a frame_info structure */ +void y4m_init_frame_info(y4m_frame_info_t *i); + +/* finalize a frame_info structure */ +void y4m_fini_frame_info(y4m_frame_info_t *i); + +/* make one frame_info into a copy of another */ +void y4m_copy_frame_info(y4m_frame_info_t *dest, + const y4m_frame_info_t *src); + +/* access frame_info xtag_list */ +y4m_xtag_list_t *y4m_fi_xtags(y4m_frame_info_t *fi); + + + +/************************************************************************ + * blocking read and write functions + * + * o guaranteed to transfer entire payload (or fail) + * o return values: + * 0 (zero) complete success + * -(# of remaining bytes) error (and errno left set) + * +(# of remaining bytes) EOF (for y4m_read only) + * + ************************************************************************/ + +/* read len bytes from fd into buf */ +ssize_t y4m_read(int fd, void *buf, size_t len); + +/* write len bytes from fd into buf */ +ssize_t y4m_write(int fd, const void *buf, size_t len); + + + +/************************************************************************ + * stream header processing functions + * + * o return values: + * Y4M_OK - success + * Y4M_ERR_* - error (see y4m_strerr() for descriptions) + * + ************************************************************************/ + +/* parse a string of stream header tags */ +int y4m_parse_stream_tags(char *s, y4m_stream_info_t *i); + +/* read a stream header from file descriptor fd */ +int y4m_read_stream_header(int fd, y4m_stream_info_t *i); + +/* write a stream header to file descriptor fd */ +int y4m_write_stream_header(int fd, const y4m_stream_info_t *i); + + + +/************************************************************************ + * frame processing functions + * + * o return values: + * Y4M_OK - success + * Y4M_ERR_* - error (see y4m_strerr() for descriptions) + * + ************************************************************************/ + +/* read a frame header from file descriptor fd */ +int y4m_read_frame_header(int fd, y4m_frame_info_t *i); + +/* write a frame header to file descriptor fd */ +int y4m_write_frame_header(int fd, const y4m_frame_info_t *i); + +/* read a complete frame (header + data) + o yuv[3] points to three buffers, one each for Y, U, V planes */ +int y4m_read_frame(int fd, const y4m_stream_info_t *si, + y4m_frame_info_t *fi, uint8_t * const yuv[3]); + +/* write a complete frame (header + data) + o yuv[3] points to three buffers, one each for Y, U, V planes */ +int y4m_write_frame(int fd, const y4m_stream_info_t *si, + const y4m_frame_info_t *fi, uint8_t * const yuv[3]); + + +/* read a complete frame (header + data), but de-interleave fields + into two separate buffers + o upper_field[3] same as yuv[3] above, but for upper field + o lower_field[3] same as yuv[3] above, but for lower field +*/ +int y4m_read_fields(int fd, const y4m_stream_info_t *si, + y4m_frame_info_t *fi, + uint8_t * const upper_field[3], + uint8_t * const lower_field[3]); + +/* write a complete frame (header + data), but interleave fields + from two separate buffers + o upper_field[3] same as yuv[3] above, but for upper field + o lower_field[3] same as yuv[3] above, but for lower field +*/ +int y4m_write_fields(int fd, const y4m_stream_info_t *si, + const y4m_frame_info_t *fi, + uint8_t * const upper_field[3], + uint8_t * const lower_field[3]); + + + +/************************************************************************ + * miscellaneous functions + ************************************************************************/ + +/* convenient dump of stream header info via mjpeg_log facility + * - each logged/printed line is prefixed by 'prefix' + */ +void y4m_log_stream_info(log_level_t level, const char *prefix, + const y4m_stream_info_t *i); + +/* convert a Y4M_ERR_* error code into mildly explanatory string */ +const char *y4m_strerr(int err); + +/* set 'allow_unknown_tag' flag for library... + o yn = 0 : unknown header tags will produce a parsing error + o yn = 1 : unknown header tags/values will produce a warning, but + are otherwise passed along via the xtags list + o yn = -1: don't change, just return current setting + + return value: previous setting of flag +*/ +int y4m_allow_unknown_tags(int yn); + + +#ifdef __cplusplus +} +#endif + +/************************************************************************ + ************************************************************************ + + Description of the (new!, forever?) YUV4MPEG2 stream format: + + STREAM consists of + o one '\n' terminated STREAM-HEADER + o unlimited number of FRAMEs + + FRAME consists of + o one '\n' terminated FRAME-HEADER + o "length" octets of planar YCrCb 4:2:0 image data + (if frame is interlaced, then the two fields are interleaved) + + + STREAM-HEADER consists of + o string "YUV4MPEG2 " (note the space after the '2') + o unlimited number of ' ' separated TAGGED-FIELDs + o '\n' line terminator + + FRAME-HEADER consists of + o string "FRAME " (note the space after the 'E') + o unlimited number of ' ' separated TAGGED-FIELDs + o '\n' line terminator + + + TAGGED-FIELD consists of + o single ascii character tag + o VALUE (which does not contain whitespace) + + VALUE consists of + o integer (base 10 ascii representation) + or o RATIO + or o single ascii character + or o generic ascii string + + RATIO consists of + o numerator (integer) + o ':' (a colon) + o denominator (integer) + + + The currently supported tags for the STREAM-HEADER: + W - [integer] frame width, pixels, should be > 0 + H - [integer] frame height, pixels, should be > 0 + I - [char] interlacing: p - progressive (none) + t - top-field-first + b - bottom-field-first + ? - unknown + F - [ratio] frame-rate, 0:0 == unknown + A - [ratio] sample (pixel) aspect ratio, 0:0 == unknown + X - [character string] 'metadata' (unparsed, but passed around) + + The currently supported tags for the FRAME-HEADER: + X - character string 'metadata' (unparsed, but passed around) + + ************************************************************************ + ************************************************************************/ + +#endif /* __YUV4MPEG_H__ */ + + diff --git a/ext/mplex/yuv4mpeg_intern.h b/ext/mplex/yuv4mpeg_intern.h new file mode 100644 index 000000000..140f9d623 --- /dev/null +++ b/ext/mplex/yuv4mpeg_intern.h @@ -0,0 +1,85 @@ +/* + * yuv4mpeg_intern.h: Internal constants for "new" YUV4MPEG streams + * + * Copyright (C) 2001 Andrew Stevens + * Copyright (C) 2001 Matthew J. Marjanovic + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __YUV4MPEG_INTERN_H__ +#define __YUV4MPEG_INTERN_H__ + + +#define Y4M_MAGIC "YUV4MPEG2" +#define Y4M_FRAME_MAGIC "FRAME" + +#define Y4M_DELIM " " /* single-character(space) separating tagged fields */ + +#define Y4M_LINE_MAX 256 /* max number of characters in a header line + (including the '\n', but not the '\0') */ + + +/* standard framerate ratios */ +#define Y4M_FPS_UNKNOWN { 0, 0 } +#define Y4M_FPS_NTSC_FILM { 24000, 1001 } +#define Y4M_FPS_FILM { 24, 1 } +#define Y4M_FPS_PAL { 25, 1 } +#define Y4M_FPS_NTSC { 30000, 1001 } +#define Y4M_FPS_30 { 30, 1 } +#define Y4M_FPS_PAL_FIELD { 50, 1 } +#define Y4M_FPS_NTSC_FIELD { 60000, 1001 } +#define Y4M_FPS_60 { 60, 1 } + +/* standard sample/pixel aspect ratios */ +#define Y4M_SAR_UNKNOWN { 0, 0 } +#define Y4M_SAR_SQUARE { 1, 1 } +#define Y4M_SAR_SQR_ANA_16_9 { 4, 3 } +#define Y4M_SAR_NTSC_CCIR601 { 10, 11 } +#define Y4M_SAR_NTSC_16_9 { 40, 33 } +#define Y4M_SAR_NTSC_SVCD_4_3 { 15, 11 } +#define Y4M_SAR_NTSC_SVCD_16_9 { 20, 11 } +#define Y4M_SAR_PAL_CCIR601 { 59, 54 } +#define Y4M_SAR_PAL_16_9 { 118, 81 } +#define Y4M_SAR_PAL_SVCD_4_3 { 59, 36 } +#define Y4M_SAR_PAL_SVCD_16_9 { 59, 27 } + +#define Y4M_SAR_MPEG1_1 Y4M_SAR_SQUARE +#define Y4M_SAR_MPEG1_2 { 10000, 6735 } +#define Y4M_SAR_MPEG1_3 { 10000, 7031 } /* Anamorphic 16:9 PAL */ +#define Y4M_SAR_MPEG1_4 { 10000, 7615 } +#define Y4M_SAR_MPEG1_5 { 10000, 8055 } +#define Y4M_SAR_MPEG1_6 { 10000, 8437 } /* Anamorphic 16:9 NTSC */ +#define Y4M_SAR_MPEG1_7 { 10000, 8935 } +#define Y4M_SAR_MPEG1_8 { 10000, 9375 } /* PAL/SECAM 4:3 */ +#define Y4M_SAR_MPEG1_9 { 10000, 9815 } +#define Y4M_SAR_MPEG1_10 { 10000, 10255 } +#define Y4M_SAR_MPEG1_11 { 10000, 10695 } +#define Y4M_SAR_MPEG1_12 { 10000, 11250 } /* NTSC 4:3 */ +#define Y4M_SAR_MPEG1_13 { 10000, 11575 } +#define Y4M_SAR_MPEG1_14 { 10000, 12015 } + +#define Y4M_DAR_UNKNOWN { 0, 0 } +#define Y4M_DAR_4_3 { 4, 3 } +#define Y4M_DAR_16_9 { 16, 9 } +#define Y4M_DAR_221_100 { 221, 100 } + +#define Y4M_DAR_MPEG2_1 { 1, 1 } +#define Y4M_DAR_MPEG2_2 { 4, 3 } +#define Y4M_DAR_MPEG2_3 { 16, 9 } +#define Y4M_DAR_MPEG2_4 { 221, 100 } + +#endif /* __YUV4MPEG_INTERN_H__ */ + diff --git a/ext/mplex/yuv4mpeg_ratio.cc b/ext/mplex/yuv4mpeg_ratio.cc new file mode 100644 index 000000000..a20a23738 --- /dev/null +++ b/ext/mplex/yuv4mpeg_ratio.cc @@ -0,0 +1,167 @@ +/* + * yuv4mpeg_ratio.c: Functions for dealing with y4m_ratio_t datatype. + * + * Copyright (C) 2001 Matthew J. Marjanovic + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include + +#include +#include "yuv4mpeg.h" +#include "yuv4mpeg_intern.h" + + +/* useful list of standard framerates */ +const y4m_ratio_t y4m_fps_UNKNOWN = Y4M_FPS_UNKNOWN; +const y4m_ratio_t y4m_fps_NTSC_FILM = Y4M_FPS_NTSC_FILM; +const y4m_ratio_t y4m_fps_FILM = Y4M_FPS_FILM; +const y4m_ratio_t y4m_fps_PAL = Y4M_FPS_PAL; +const y4m_ratio_t y4m_fps_NTSC = Y4M_FPS_NTSC; +const y4m_ratio_t y4m_fps_30 = Y4M_FPS_30; +const y4m_ratio_t y4m_fps_PAL_FIELD = Y4M_FPS_PAL_FIELD; +const y4m_ratio_t y4m_fps_NTSC_FIELD = Y4M_FPS_NTSC_FIELD; +const y4m_ratio_t y4m_fps_60 = Y4M_FPS_60; + +/* useful list of standard sample aspect ratios */ +const y4m_ratio_t y4m_sar_UNKNOWN = Y4M_SAR_UNKNOWN; +const y4m_ratio_t y4m_sar_SQUARE = Y4M_SAR_SQUARE; +const y4m_ratio_t y4m_sar_SQR_ANA_16_9 = Y4M_SAR_SQR_ANA_16_9; +const y4m_ratio_t y4m_sar_NTSC_CCIR601 = Y4M_SAR_NTSC_CCIR601; +const y4m_ratio_t y4m_sar_NTSC_16_9 = Y4M_SAR_NTSC_16_9; +const y4m_ratio_t y4m_sar_NTSC_SVCD_4_3 = Y4M_SAR_NTSC_SVCD_4_3; +const y4m_ratio_t y4m_sar_NTSC_SVCD_16_9 = Y4M_SAR_NTSC_SVCD_16_9; +const y4m_ratio_t y4m_sar_PAL_CCIR601 = Y4M_SAR_PAL_CCIR601; +const y4m_ratio_t y4m_sar_PAL_16_9 = Y4M_SAR_PAL_16_9; +const y4m_ratio_t y4m_sar_PAL_SVCD_4_3 = Y4M_SAR_PAL_SVCD_4_3; +const y4m_ratio_t y4m_sar_PAL_SVCD_16_9 = Y4M_SAR_PAL_SVCD_16_9; + +/* useful list of standard display aspect ratios */ +const y4m_ratio_t y4m_dar_4_3 = Y4M_DAR_4_3; +const y4m_ratio_t y4m_dar_16_9 = Y4M_DAR_16_9; +const y4m_ratio_t y4m_dar_221_100 = Y4M_DAR_221_100; + +/* + * Euler's algorithm for greatest common divisor + */ + +static int +gcd (int a, int b) +{ + a = (a >= 0) ? a : -a; + b = (b >= 0) ? b : -b; + + while (b > 0) { + int x = b; + + b = a % b; + a = x; + } + return a; +} + + +/************************************************************************* + * + * Remove common factors from a ratio + * + *************************************************************************/ + + +void +y4m_ratio_reduce (y4m_ratio_t * r) +{ + int d; + + if ((r->n == 0) && (r->d == 0)) + return; /* "unknown" */ + d = gcd (r->n, r->d); + r->n /= d; + r->d /= d; +} + + + +/************************************************************************* + * + * Parse "nnn:ddd" into a ratio + * + * returns: Y4M_OK - success + * Y4M_ERR_RANGE - range error + * + *************************************************************************/ + +int +y4m_parse_ratio (y4m_ratio_t * r, const char *s) +{ + char *t = strchr (s, ':'); + + if (t == NULL) + return Y4M_ERR_RANGE; + r->n = atoi (s); + r->d = atoi (t + 1); + if (r->d < 0) + return Y4M_ERR_RANGE; + /* 0:0 == unknown, so that is ok, otherwise zero denominator is bad */ + if ((r->d == 0) && (r->n != 0)) + return Y4M_ERR_RANGE; + y4m_ratio_reduce (r); + return Y4M_OK; +} + + + +/************************************************************************* + * + * Guess the true SAR (sample aspect ratio) from a list of commonly + * encountered values, given the "suggested" display aspect ratio, and + * the true frame width and height. + * + * Returns y4m_sar_UNKNOWN if no match is found. + * + *************************************************************************/ + +/* this is big enough to accommodate the difference between 720 and 704 */ +#define GUESS_ASPECT_TOLERANCE 0.03 + +y4m_ratio_t +y4m_guess_sar (int width, int height, y4m_ratio_t dar) +{ + int i; + double implicit_sar = (double) (dar.n * height) / (double) (dar.d * width); + y4m_ratio_t sarray[] = { + y4m_sar_SQUARE, + y4m_sar_NTSC_CCIR601, + y4m_sar_NTSC_16_9, + y4m_sar_NTSC_SVCD_4_3, + y4m_sar_NTSC_SVCD_16_9, + y4m_sar_PAL_CCIR601, + y4m_sar_PAL_16_9, + y4m_sar_PAL_SVCD_4_3, + y4m_sar_PAL_SVCD_16_9, + y4m_sar_UNKNOWN + }; + + for (i = 0; !(Y4M_RATIO_EQL (sarray[i], y4m_sar_UNKNOWN)); i++) { + double ratio = implicit_sar / Y4M_RATIO_DBL (sarray[i]); + + if ((ratio > (1.0 - GUESS_ASPECT_TOLERANCE)) && (ratio < (1.0 + GUESS_ASPECT_TOLERANCE))) + return sarray[i]; + } + return y4m_sar_UNKNOWN; +} -- cgit v1.2.1