diff --git a/codec/h264/decode/.gitignore b/codec/h264/decode/.gitignore new file mode 100644 index 00000000..86ede22e --- /dev/null +++ b/codec/h264/decode/.gitignore @@ -0,0 +1,3 @@ +.*.swp +*.mp4 +*.h264 diff --git a/codec/h264/decode/CODEOWNERS b/codec/h264/decode/CODEOWNERS new file mode 100644 index 00000000..2ba2c957 --- /dev/null +++ b/codec/h264/decode/CODEOWNERS @@ -0,0 +1,2 @@ +# Default owners for everything in this repo. +* @scruzin @saxon-milton diff --git a/codec/h264/decode/LICENSE b/codec/h264/decode/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/codec/h264/decode/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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 + + 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) + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) year 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 Lesser General +Public License instead of this License. diff --git a/codec/h264/decode/README.md b/codec/h264/decode/README.md new file mode 100644 index 00000000..6a5e673b --- /dev/null +++ b/codec/h264/decode/README.md @@ -0,0 +1,44 @@ +[![Build Status](https://travis-ci.org/ausocean/h264decode.svg?branch=master)](https://travis-ci.org/ausocean/h264decode) [![Go Report Card](https://goreportcard.com/badge/github.com/ausocean/h264decode)](https://goreportcard.com/report/github.com/ausocean/h264decode) + +Listens for a stream of H264 bytes on port 8000. + +A stream is read sequentially dropping each NAL into a struct with access to the RBSP and seekable features. No interface contracts are implemented right now. This is heavily a work in progress. + +# TODO + +* CABAC initialization +* Context-adaptive arithmetic entropy-coded syntax element support +* Macroblock to YCbCr image decoding + +## Done + +* DecodeBypass, 9.3.3.2.3 +* DecodeTerminate, 9.3.3.2.4 +* RenormD - 9.3.3.2.2 +* rangeTableLPS ( Table 9-44 ) +* Derive ctxIDX per 9.3.3.1 +* Select M, N values + +### ArithmeticDecoding S 9.3.3.2 + +* cabac.go : ArithmeticDecoding 9.3.3.3.2 +* cabac.go : DecodeBypass, DecodeTerminate, DecodeDecision + +## In Progress + +* Make use of DecodeBypass and DecodeTerminate information +* 9.3.3.2.1 - BinaryDecision + * 9.3.3.2.1.1, 9.3.3.2.2 + +## Next + +* Make use of initCabac (initialized CABACs) + +# Background + +The last point was and is the entire driving force behind this project: To decode a single frame to an image and begin doing computer vision tasks on it. A while back, this project was started to keep an eye on rodents moving their way around various parts of our house and property. What was supposed to happen was motion detected from one-frame to another of an MJPEG stream would trigger capturing the stream. Analyzing the stream, even down at 24 fps, caused captures to be triggered too late. When it was triggered, there was so much blur in the resulting captured stream, it wasn't very watchable. + +Doing a little prototyping it was apparent reading an h.264 stream into VLC provided a watchable, unblurry, video. With a little searching on the internet, (https://github.com/gqf2008/codec) provided an example of using [ffMPEG](https://www.ffmpeg.org/) to decode an h.264 frame/NAL unit's macroblocks into a YCbCr image. Were this some shippable product with deadlines and things, [GGF2008](https://github.com/gqf2008/codec) would've been wired up and progress would've happened. But this is a tinkering project. Improving development skills, learning a bit about streaming deata, and filling dead-air were the criteria for this project. + +Because of that, a pure Go h.264 stream decoder was the only option. Duh. + diff --git a/codec/h264/decode/bits/bitreader.go b/codec/h264/decode/bits/bitreader.go new file mode 100644 index 00000000..7e17f407 --- /dev/null +++ b/codec/h264/decode/bits/bitreader.go @@ -0,0 +1,170 @@ +/* +DESCRIPTION + bitreader.go provides a bit reader implementation that can read or peek from + an io.Reader data source. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + +LICENSE + This code is a modification of code from: + https://golang.org/src/compress/bzip2/bit_reader.go. + + Copyright (c) 2009 The Go Authors. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2017-2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It 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 + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +// Package bits provides a bit reader implementation that can read or peek from +// an io.Reader data source. +package bits + +import ( + "bufio" + "io" +) + +type bytePeeker interface { + io.ByteReader + Peek(int) ([]byte, error) +} + +// BitReader is a bit reader that provides methods for reading bits from an +// io.Reader source. +type BitReader struct { + r bytePeeker + n uint64 + bits int + nRead int +} + +// NewBitReader returns a new BitReader. +func NewBitReader(r io.Reader) *BitReader { + byter, ok := r.(bytePeeker) + if !ok { + byter = bufio.NewReader(r) + } + return &BitReader{r: byter} +} + +// ReadBits reads n bits from the source and returns them the least-significant +// part of a uint64. +// For example, with a source as []byte{0x8f,0xe3} (1000 1111, 1110 0011), we +// would get the following results for consequtive reads with n values: +// n = 4, res = 0x8 (1000) +// n = 2, res = 0x3 (0011) +// n = 4, res = 0xf (1111) +// n = 6, res = 0x23 (0010 0011) +func (br *BitReader) ReadBits(n int) (uint64, error) { + for n > br.bits { + b, err := br.r.ReadByte() + if err == io.EOF { + return 0, io.ErrUnexpectedEOF + } + if err != nil { + return 0, err + } + br.nRead++ + br.n <<= 8 + br.n |= uint64(b) + br.bits += 8 + } + + // br.n looks like this (assuming that br.bits = 14 and bits = 6): + // Bit: 111111 + // 5432109876543210 + // + // (6 bits, the desired output) + // |-----| + // V V + // 0101101101001110 + // ^ ^ + // |------------| + // br.bits (num valid bits) + // + // This the next line right shifts the desired bits into the + // least-significant places and masks off anything above. + r := (br.n >> uint(br.bits-n)) & ((1 << uint(n)) - 1) + br.bits -= n + return r, nil +} + +// PeekBits provides the next n bits returning them in the least-significant +// part of a uint64, without advancing through the source. +// For example, with a source as []byte{0x8f,0xe3} (1000 1111, 1110 0011), we +// would get the following results for consequtive peeks with n values: +// n = 4, res = 0x8 (1000) +// n = 8, res = 0x8f (1000 1111) +// n = 16, res = 0x8fe3 (1000 1111, 1110 0011) +func (br *BitReader) PeekBits(n int) (uint64, error) { + byt, err := br.r.Peek(int((n-br.bits)+7) / 8) + bits := br.bits + if err != nil { + if err == io.EOF { + return 0, io.ErrUnexpectedEOF + } + return 0, err + } + for i := 0; n > bits; i++ { + b := byt[i] + if err != nil { + return 0, err + } + br.n <<= 8 + br.n |= uint64(b) + bits += 8 + } + + r := (br.n >> uint(bits-n)) & ((1 << uint(n)) - 1) + return r, nil +} + +// ByteAligned returns true if the reader position is at the start of a byte, +// and false otherwise. +func (br *BitReader) ByteAligned() bool { + return br.bits == 0 +} + +// BytesRead returns the number of bytes that have been read by the BitReader. +func (br *BitReader) BytesRead() int { + return br.nRead +} diff --git a/codec/h264/decode/bits/bitreader_test.go b/codec/h264/decode/bits/bitreader_test.go new file mode 100644 index 00000000..79a83635 --- /dev/null +++ b/codec/h264/decode/bits/bitreader_test.go @@ -0,0 +1,311 @@ +/* +DESCRIPTION + bitreader_test.go provides testing for functionality defined in bitreader.go. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + +LICENSE + Copyright (C) 2017-2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It 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 + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package bits + +import ( + "bytes" + "fmt" + "io" + "reflect" + "testing" +) + +func TestReadBits(t *testing.T) { + tests := []struct { + in []byte // The bytes the source io.Reader will be initialised with. + n []int // The values of n for the reads we wish to do. + want []uint64 // The results we expect for each ReadBits call. + err []error // The error expected from each ReadBits call. + }{ + { + in: []byte{0xff}, + n: []int{8}, + want: []uint64{0xff}, + err: []error{nil}, + }, + { + in: []byte{0xff}, + n: []int{4, 4}, + want: []uint64{0x0f, 0x0f}, + err: []error{nil, nil}, + }, + { + in: []byte{0xff}, + n: []int{1, 7}, + want: []uint64{0x01, 0x7f}, + err: []error{nil, nil}, + }, + { + in: []byte{0xff, 0xff}, + n: []int{8, 8}, + want: []uint64{0xff, 0xff}, + err: []error{nil, nil}, + }, + { + in: []byte{0xff, 0xff}, + n: []int{8, 10}, + want: []uint64{0xff, 0}, + err: []error{nil, io.ErrUnexpectedEOF}, + }, + { + in: []byte{0xff, 0xff}, + n: []int{4, 8, 4}, + want: []uint64{0x0f, 0xff, 0x0f}, + err: []error{nil, nil, nil}, + }, + { + in: []byte{0xff, 0xff}, + n: []int{16}, + want: []uint64{0xffff}, + err: []error{nil}, + }, + { + in: []byte{0x8f, 0xe3}, + n: []int{4, 2, 4, 6}, + want: []uint64{0x8, 0x3, 0xf, 0x23}, + err: []error{nil, nil, nil, nil}, + }, + } + + for i, test := range tests { + br := NewBitReader(bytes.NewReader(test.in)) + + // For each value of n defined in test.reads, we call br.ReadBits, collect + // the result and check the error. + var got []uint64 + for j, n := range test.n { + bits, err := br.ReadBits(n) + if err != test.err[j] { + t.Fatalf("did not expect error: %v for read: %d test: %d", err, j, i) + } + got = append(got, bits) + } + + // Now we can check the read results. + if !reflect.DeepEqual(got, test.want) { + t.Errorf("did not get expected results from ReadBits for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +func TestPeekBits(t *testing.T) { + tests := []struct { + in []byte + n []int + want []uint64 + err []error + }{ + { + in: []byte{0xff}, + n: []int{8}, + want: []uint64{0xff}, + err: []error{nil}, + }, + { + in: []byte{0x8f, 0xe3}, + n: []int{4, 8, 16}, + want: []uint64{0x8, 0x8f, 0x8fe3}, + err: []error{nil, nil, nil}, + }, + { + in: []byte{0x8f, 0xe3, 0x8f, 0xe3}, + n: []int{32}, + want: []uint64{0x8fe38fe3}, + err: []error{nil}, + }, + { + in: []byte{0x8f, 0xe3}, + n: []int{3, 5, 10}, + want: []uint64{0x4, 0x11, 0x23f}, + err: []error{nil, nil, nil}, + }, + { + in: []byte{0x8f, 0xe3}, + n: []int{3, 20, 10}, + want: []uint64{0x4, 0, 0x23f}, + err: []error{nil, io.ErrUnexpectedEOF, nil}, + }, + } + + for i, test := range tests { + br := NewBitReader(bytes.NewReader(test.in)) + + // Call PeekBits for each value of n defined in test. + var got []uint64 + for j, n := range test.n { + bits, err := br.PeekBits(n) + if err != test.err[j] { + t.Fatalf("did not expect error: %v for peek: %d test: %d", err, j, i) + } + got = append(got, bits) + } + + // Now we can check the peek results. + if !reflect.DeepEqual(got, test.want) { + t.Errorf("did not get expected results from PeekBits for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +func TestReadOrPeek(t *testing.T) { + // The possible operations we might make. + const ( + read = iota + peek + ) + + tests := []struct { + in []byte // The bytes the source io.Reader will be initialised with. + op []int // The series of operations we want to perform (read or peek). + n []int // The values of n for the reads/peeks we wish to do. + want []uint64 // The results we expect for each ReadBits call. + }{ + { + in: []byte{0x8f, 0xe3, 0x8f, 0xe3}, + op: []int{read, peek, peek, read, peek}, + n: []int{13, 3, 3, 7, 12}, + want: []uint64{0x11fc, 0x3, 0x3, 0x38, 0xfe3}, + }, + } + + for i, test := range tests { + br := NewBitReader(bytes.NewReader(test.in)) + + var ( + bits uint64 + got []uint64 + err error + ) + + // Go through the operations we wish to perform for this test and collect + // results/errors. + for j, op := range test.op { + switch op { + case read: + bits, err = br.ReadBits(test.n[j]) + case peek: + bits, err = br.PeekBits(test.n[j]) + default: + panic(fmt.Sprintf("bad test: invalid operation: %d", op)) + } + got = append(got, bits) + if err != nil { + t.Fatalf("did not expect error: %v for operation: %d test: %d", err, j, i) + } + } + + // Now we can check the results from the reads/peeks. + if !reflect.DeepEqual(got, test.want) { + t.Errorf("did not get expected results for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +func TestByteAligned(t *testing.T) { + tests := []struct { + in []byte + n []int + want bool + }{ + { + in: []byte{0xff}, + n: []int{1}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{2}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{3}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{4}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{5}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{6}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{7}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{8}, + want: true, + }, + { + in: []byte{0xff, 0xff}, + n: []int{9}, + want: false, + }, + { + in: []byte{0xff, 0xff}, + n: []int{16}, + want: true, + }, + { + in: []byte{0xff, 0xff}, + n: []int{5, 2}, + want: false, + }, + { + in: []byte{0xff, 0xff}, + n: []int{5, 3}, + want: true, + }, + } + + for i, test := range tests { + br := NewBitReader(bytes.NewReader(test.in)) + + // Call ReadBits for each value of n defined in test. + for j, n := range test.n { + _, err := br.ReadBits(n) + if err != nil { + t.Fatalf("did not expect error: %v for ReadBits: %d test: %d", err, j, i) + } + } + + // Now check br.ByteAligned. + got := br.ByteAligned() + if got != test.want { + t.Errorf("did not get expected results from ByteAligned for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} diff --git a/codec/h264/decode/cabac.go b/codec/h264/decode/cabac.go new file mode 100644 index 00000000..3697f630 --- /dev/null +++ b/codec/h264/decode/cabac.go @@ -0,0 +1,691 @@ +package h264 + +import ( + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +const ( + NaCtxId = 10000 + NA_SUFFIX = -1 + MbAddrNotAvailable = 10000 +) + +// G.7.4.3.4 via G.7.3.3.4 via 7.3.2.13 for NalUnitType 20 or 21 +// refLayerMbWidthC is equal to MbWidthC for the reference layer representation +func RefMbW(chromaFlag, refLayerMbWidthC int) int { + if chromaFlag == 0 { + return 16 + } + return refLayerMbWidthC +} + +// refLayerMbHeightC is equal to MbHeightC for the reference layer representation +func RefMbH(chromaFlag, refLayerMbHeightC int) int { + if chromaFlag == 0 { + return 16 + } + return refLayerMbHeightC +} +func XOffset(xRefMin16, refMbW int) int { + return (((xRefMin16 - 64) >> 8) << 4) - (refMbW >> 1) +} +func YOffset(yRefMin16, refMbH int) int { + return (((yRefMin16 - 64) >> 8) << 4) - (refMbH >> 1) +} +func MbWidthC(sps *SPS) int { + mbWidthC := 16 / SubWidthC(sps) + if sps.ChromaFormat == chromaMonochrome || sps.UseSeparateColorPlane { + mbWidthC = 0 + } + return mbWidthC +} +func MbHeightC(sps *SPS) int { + mbHeightC := 16 / SubHeightC(sps) + if sps.ChromaFormat == chromaMonochrome || sps.UseSeparateColorPlane { + mbHeightC = 0 + } + return mbHeightC +} + +// G.8.6.2.2.2 +func Xr(x, xOffset, refMbW int) int { + return (x + xOffset) % refMbW +} +func Yr(y, yOffset, refMbH int) int { + return (y + yOffset) % refMbH +} + +// G.8.6.2.2.2 +func Xd(xr, refMbW int) int { + if xr >= refMbW/2 { + return xr - refMbW + } + return xr + 1 +} +func Yd(yr, refMbH int) int { + if yr >= refMbH/2 { + return yr - refMbH + } + return yr + 1 +} +func Ya(yd, refMbH, signYd int) int { + return yd - (refMbH/2+1)*signYd +} + +// 6.4.11.1 +func MbAddr(xd, yd, predPartWidth int) { + // TODO: Unfinished + var n string + if xd == -1 && yd == 0 { + n = "A" + } + if xd == 0 && yd == -1 { + n = "B" + } + if xd == predPartWidth && yd == -1 { + n = "C" + } + if xd == -1 && yd == -1 { + n = "D" + } + _ = n +} + +func CondTermFlag(mbAddr, mbSkipFlag int) int { + if mbAddr == MbAddrNotAvailable || mbSkipFlag == 1 { + return 0 + } + return 1 +} + +// s9.3.3 p 278: Returns the value of the syntax element +func (bin *Binarization) Decode(sliceContext *SliceContext, b *bits.BitReader, rbsp []byte) { + if bin.SyntaxElement == "MbType" { + bin.binString = binIdxMbMap[sliceContext.Slice.Data.SliceTypeName][sliceContext.Slice.Data.MbType] + } else { + logger.Printf("TODO: no means to find binString for %s\n", bin.SyntaxElement) + } +} + +// 9.3.3.1.1 : returns ctxIdxInc +func Decoder9_3_3_1_1_1(condTermFlagA, condTermFlagB int) int { + return condTermFlagA + condTermFlagB +} + +// 9-5 +// 7-30 p 112 +func SliceQPy(pps *PPS, header *SliceHeader) int { + return 26 + pps.PicInitQpMinus26 + header.SliceQpDelta +} + +// 9-5 +func PreCtxState(m, n, sliceQPy int) int { + // slicQPy-subY + return Clip3(1, 126, ((m*Clip3(0, 51, sliceQPy))>>4)+n) +} + +func Clip1y(x, bitDepthY int) int { + return Clip3(0, (1< y { + return y + } + return z +} + +type CABAC struct { + PStateIdx int + ValMPS int + Context *SliceContext +} + +// table 9-1 +func initCabac(binarization *Binarization, context *SliceContext) *CABAC { + var valMPS, pStateIdx int + // TODO: When to use prefix, when to use suffix? + ctxIdx := CtxIdx( + binarization.binIdx, + binarization.MaxBinIdxCtx.Prefix, + binarization.CtxIdxOffset.Prefix) + mn := MNVars[ctxIdx] + + preCtxState := PreCtxState(mn[0].M, mn[0].N, SliceQPy(context.PPS, context.Header)) + if preCtxState <= 63 { + pStateIdx = 63 - preCtxState + valMPS = 0 + } else { + pStateIdx = preCtxState - 64 + valMPS = 1 + } + _ = pStateIdx + _ = valMPS + // Initialization of context variables + // Initialization of decoding engine + return &CABAC{ + PStateIdx: pStateIdx, + ValMPS: valMPS, + Context: context, + } +} + +// Table 9-36, 9-37 +// func BinIdx(mbType int, sliceTypeName string) []int { +// Map of SliceTypeName[MbType][]int{binString} +// {"SliceTypeName": {MbTypeCode: []BinString}} +var ( + binIdxMbMap = map[string]map[int][]int{ + "I": { + 0: {0}, + 1: {1, 0, 0, 0, 0, 0}, + 2: {1, 0, 0, 0, 0, 1}, + 3: {1, 0, 0, 0, 1, 0}, + 4: {1, 0, 0, 0, 1, 1}, + 5: {1, 0, 0, 1, 0, 0, 0}, + 6: {1, 0, 0, 1, 0, 0, 1}, + 7: {1, 0, 0, 1, 0, 1, 0}, + 8: {1, 0, 0, 1, 0, 1, 1}, + 9: {1, 0, 0, 1, 1, 0, 0}, + 10: {1, 0, 0, 1, 1, 0, 1}, + 11: {1, 0, 0, 1, 1, 1, 0}, + 12: {1, 0, 0, 1, 1, 1, 1}, + 13: {1, 0, 1, 0, 0, 0}, + 14: {1, 0, 1, 0, 0, 1}, + 15: {1, 0, 1, 0, 1, 0}, + 16: {1, 0, 1, 0, 1, 1}, + 17: {1, 0, 1, 1, 0, 0, 0}, + 18: {1, 0, 1, 1, 0, 0, 1}, + 19: {1, 0, 1, 1, 0, 1, 0}, + 20: {1, 0, 1, 1, 0, 1, 1}, + 21: {1, 0, 1, 1, 1, 0, 0}, + 22: {1, 0, 1, 1, 1, 0, 1}, + 23: {1, 0, 1, 1, 1, 1, 0}, + 24: {1, 0, 1, 1, 1, 1, 1}, + 25: {1, 1}, + }, + // Table 9-37 + "P": { + 0: {0, 0, 0}, + 1: {0, 1, 1}, + 2: {0, 1, 0}, + 3: {0, 0, 1}, + 4: {}, + 5: {1}, + 6: {1}, + 7: {1}, + 8: {1}, + 9: {1}, + 10: {1}, + 11: {1}, + 12: {1}, + 13: {1}, + 14: {1}, + 15: {1}, + 16: {1}, + 17: {1}, + 18: {1}, + 19: {1}, + 20: {1}, + 21: {1}, + 22: {1}, + 23: {1}, + 24: {1}, + 25: {1}, + 26: {1}, + 27: {1}, + 28: {1}, + 29: {1}, + 30: {1}, + }, + // Table 9-37 + "SP": { + 0: {0, 0, 0}, + 1: {0, 1, 1}, + 2: {0, 1, 0}, + 3: {0, 0, 1}, + 4: {}, + 5: {1}, + 6: {1}, + 7: {1}, + 8: {1}, + 9: {1}, + 10: {1}, + 11: {1}, + 12: {1}, + 13: {1}, + 14: {1}, + 15: {1}, + 16: {1}, + 17: {1}, + 18: {1}, + 19: {1}, + 20: {1}, + 21: {1}, + 22: {1}, + 23: {1}, + 24: {1}, + 25: {1}, + 26: {1}, + 27: {1}, + 28: {1}, + 29: {1}, + 30: {1}, + }, + // TODO: B Slice table 9-37 + } + + // Map of SliceTypeName[SubMbType][]int{binString} + binIdxSubMbMap = map[string]map[int][]int{ + "P": { + 0: {1}, + 1: {0, 0}, + 2: {0, 1, 1}, + 3: {0, 1, 0}, + }, + "SP": { + 0: {1}, + 1: {0, 0}, + 2: {0, 1, 1}, + 3: {0, 1, 0}, + }, + // TODO: B slice table 9-38 + } + + // Table 9-36, 9-37 + MbBinIdx = []int{1, 2, 3, 4, 5, 6} + + // Table 9-38 + SubMbBinIdx = []int{0, 1, 2, 3, 4, 5} +) + +// Table 9-34 +type MaxBinIdxCtx struct { + // When false, Prefix is the MaxBinIdxCtx + IsPrefixSuffix bool + Prefix, Suffix int +} +type CtxIdxOffset struct { + // When false, Prefix is the MaxBinIdxCtx + IsPrefixSuffix bool + Prefix, Suffix int +} + +// Table 9-34 +type Binarization struct { + SyntaxElement string + BinarizationType + MaxBinIdxCtx + CtxIdxOffset + UseDecodeBypass int + // TODO: Why are these private but others aren't? + binIdx int + binString []int +} +type BinarizationType struct { + PrefixSuffix bool + FixedLength bool + Unary bool + TruncatedUnary bool + CMax bool + // 9.3.2.3 + UEGk bool + CMaxValue int +} + +// 9.3.2.5 +func NewBinarization(syntaxElement string, data *SliceData) *Binarization { + sliceTypeName := data.SliceTypeName + logger.Printf("debug: binarization of %s in sliceType %s\n", syntaxElement, sliceTypeName) + binarization := &Binarization{SyntaxElement: syntaxElement} + switch syntaxElement { + case "CodedBlockPattern": + binarization.BinarizationType = BinarizationType{PrefixSuffix: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{IsPrefixSuffix: true, Prefix: 3, Suffix: 1} + binarization.CtxIdxOffset = CtxIdxOffset{IsPrefixSuffix: true, Prefix: 73, Suffix: 77} + case "IntraChromaPredMode": + binarization.BinarizationType = BinarizationType{ + TruncatedUnary: true, CMax: true, CMaxValue: 3} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 1} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 64} + case "MbQpDelta": + binarization.BinarizationType = BinarizationType{} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 2} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 60} + case "MvdLnEnd0": + binarization.UseDecodeBypass = 1 + binarization.BinarizationType = BinarizationType{UEGk: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{IsPrefixSuffix: true, Prefix: 4, Suffix: NA_SUFFIX} + binarization.CtxIdxOffset = CtxIdxOffset{ + IsPrefixSuffix: true, + Prefix: 40, + Suffix: NA_SUFFIX, + } + case "MvdLnEnd1": + binarization.UseDecodeBypass = 1 + binarization.BinarizationType = BinarizationType{UEGk: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{ + IsPrefixSuffix: true, + Prefix: 4, + Suffix: NA_SUFFIX, + } + binarization.CtxIdxOffset = CtxIdxOffset{ + IsPrefixSuffix: true, + Prefix: 47, + Suffix: NA_SUFFIX, + } + // 9.3.2.5 + case "MbType": + logger.Printf("debug: \tMbType is %s\n", data.MbTypeName) + switch sliceTypeName { + case "SI": + binarization.BinarizationType = BinarizationType{PrefixSuffix: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{IsPrefixSuffix: true, Prefix: 0, Suffix: 6} + binarization.CtxIdxOffset = CtxIdxOffset{IsPrefixSuffix: true, Prefix: 0, Suffix: 3} + case "I": + binarization.BinarizationType = BinarizationType{} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 6} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 3} + case "SP": + fallthrough + case "P": + binarization.BinarizationType = BinarizationType{PrefixSuffix: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{IsPrefixSuffix: true, Prefix: 2, Suffix: 5} + binarization.CtxIdxOffset = CtxIdxOffset{IsPrefixSuffix: true, Prefix: 14, Suffix: 17} + } + case "MbFieldDecodingFlag": + binarization.BinarizationType = BinarizationType{ + FixedLength: true, CMax: true, CMaxValue: 1} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 0} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 70} + case "PrevIntra4x4PredModeFlag": + fallthrough + case "PrevIntra8x8PredModeFlag": + binarization.BinarizationType = BinarizationType{FixedLength: true, CMax: true, CMaxValue: 1} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 0} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 68} + case "RefIdxL0": + fallthrough + case "RefIdxL1": + binarization.BinarizationType = BinarizationType{Unary: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 2} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 54} + case "RemIntra4x4PredMode": + fallthrough + case "RemIntra8x8PredMode": + binarization.BinarizationType = BinarizationType{FixedLength: true, CMax: true, CMaxValue: 7} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 0} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 69} + case "TransformSize8x8Flag": + binarization.BinarizationType = BinarizationType{FixedLength: true, CMax: true, CMaxValue: 1} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 0} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 399} + } + return binarization +} +func (b *Binarization) IsBinStringMatch(bits []int) bool { + for i, _b := range bits { + if b.binString[i] != _b { + return false + } + } + return len(b.binString) == len(bits) +} + +// 9.3.1.2: output is codIRange and codIOffset +func initDecodingEngine(bitReader *bits.BitReader) (int, int, error) { + logger.Printf("debug: initializing arithmetic decoding engine\n") + codIRange := 510 + codIOffset, err := bitReader.ReadBits(9) + if err != nil { + return 0, 0, errors.Wrap(err, "could not read codIOffset") + } + logger.Printf("debug: codIRange: %d :: codIOffsset: %d\n", codIRange, codIOffset) + return codIRange, int(codIOffset), nil +} + +// 9.3.3.2: output is value of the bin +func NewArithmeticDecoding(context *SliceContext, binarization *Binarization, ctxIdx, codIRange, codIOffset int) (ArithmeticDecoding, error) { + a := ArithmeticDecoding{Context: context, Binarization: binarization} + logger.Printf("debug: decoding bypass %d, for ctx %d\n", binarization.UseDecodeBypass, ctxIdx) + // TODO: Implement + if binarization.UseDecodeBypass == 1 { + // TODO: 9.3.3.2.3 : DecodeBypass() + var err error + codIOffset, a.BinVal, err = a.DecodeBypass(context.Slice.Data, codIRange, codIOffset) + if err != nil { + return ArithmeticDecoding{}, errors.Wrap(err, "error from DecodeBypass getting codIOffset and BinVal") + } + + } else if binarization.UseDecodeBypass == 0 && ctxIdx == 276 { + // TODO: 9.3.3.2.4 : DecodeTerminate() + } else { + // TODO: 9.3.3.2.1 : DecodeDecision() + } + a.BinVal = -1 + return a, nil +} + +// 9.3.3.2.3 +// Invoked when bypassFlag is equal to 1 +func (a ArithmeticDecoding) DecodeBypass(sliceData *SliceData, codIRange, codIOffset int) (int, int, error) { + // Decoded value binVal + codIOffset = codIOffset << uint(1) + // TODO: Concurrency check + // TODO: Possibly should be codIOffset | ReadOneBit + shift, err := sliceData.BitReader.ReadBits(1) + if err != nil { + return 0, 0, errors.Wrap(err, "coult not read shift bit from sliceData.") + } + codIOffset = codIOffset << uint(shift) + if codIOffset >= codIRange { + a.BinVal = 1 + codIOffset -= codIRange + } else { + a.BinVal = 0 + } + return codIOffset, a.BinVal, nil +} + +// 9.3.3.2.4 +// Decodes endOfSliceFlag and I_PCM +// Returns codIRange, codIOffSet, decoded value of binVal +func (a ArithmeticDecoding) DecodeTerminate(sliceData *SliceData, codIRange, codIOffset int) (int, int, int, error) { + codIRange -= 2 + if codIOffset >= codIRange { + a.BinVal = 1 + // Terminate CABAC decoding, last bit inserted into codIOffset is = 1 + // this is now also the rbspStopOneBit + // TODO: How is this denoting termination? + return codIRange, codIOffset, a.BinVal, nil + } + a.BinVal = 0 + var err error + codIRange, codIOffset, err = a.RenormD(sliceData, codIRange, codIOffset) + if err != nil { + return 0, 0, 0, errors.Wrap(err, "error from RenormD") + } + return codIRange, codIOffset, a.BinVal, nil +} + +// 9.3.3.2.2 Renormalization process of ADecEngine +// Returns codIRange, codIOffset +func (a ArithmeticDecoding) RenormD(sliceData *SliceData, codIRange, codIOffset int) (int, int, error) { + if codIRange >= 256 { + return codIRange, codIOffset, nil + } + codIRange = codIRange << uint(1) + codIOffset = codIOffset << uint(1) + bit, err := sliceData.BitReader.ReadBits(1) + if err != nil { + return 0, 0, errors.Wrap(err, "could not read bit from sliceData") + } + codIOffset = codIOffset | int(bit) + return a.RenormD(sliceData, codIRange, codIOffset) +} + +type ArithmeticDecoding struct { + Context *SliceContext + Binarization *Binarization + BinVal int +} + +// 9.3.3.2.1 +// returns: binVal, updated codIRange, updated codIOffset +func (a ArithmeticDecoding) BinaryDecision(ctxIdx, codIRange, codIOffset int) (int, int, int, error) { + var binVal int + cabac := initCabac(a.Binarization, a.Context) + // Derivce codIRangeLPS + qCodIRangeIdx := (codIRange >> 6) & 3 + pStateIdx := cabac.PStateIdx + codIRangeLPS, err := retCodIRangeLPS(pStateIdx, qCodIRangeIdx) + if err != nil { + return 0, 0, 0, errors.Wrap(err, "could not get codIRangeLPS from retCodIRangeLPS") + } + + codIRange = codIRange - codIRangeLPS + if codIOffset >= codIRange { + binVal = 1 - cabac.ValMPS + codIOffset -= codIRange + codIRange = codIRangeLPS + } else { + binVal = cabac.ValMPS + } + + // TODO: Do StateTransition and then RenormD happen here? See: 9.3.3.2.1 + return binVal, codIRange, codIOffset, nil +} + +// 9.3.3.2.1.1 +// Returns: pStateIdx, valMPS +func (c *CABAC) StateTransitionProcess(binVal int) { + if binVal == c.ValMPS { + c.PStateIdx = stateTransxTab[c.PStateIdx].TransIdxMPS + } else { + if c.PStateIdx == 0 { + c.ValMPS = 1 - c.ValMPS + } + c.PStateIdx = stateTransxTab[c.PStateIdx].TransIdxLPS + } +} + +var ctxIdxLookup = map[int]map[int]int{ + 3: {0: NaCtxId, 1: 276, 2: 3, 3: 4, 4: NaCtxId, 5: NaCtxId}, + 14: {0: 0, 1: 1, 2: NaCtxId}, + 17: {0: 0, 1: 276, 2: 1, 3: 2, 4: NaCtxId}, + 27: {0: NaCtxId, 1: 3, 2: NaCtxId}, + 32: {0: 0, 1: 276, 2: 1, 3: 2, 4: NaCtxId}, + 36: {2: NaCtxId, 3: 3, 4: 3, 5: 3}, + 40: {0: NaCtxId}, + 47: {0: NaCtxId, 1: 3, 2: 4, 3: 5}, + 54: {0: NaCtxId, 1: 4}, + 64: {0: NaCtxId, 1: 3, 2: 3}, + 69: {0: 0, 1: 0, 2: 0}, + 77: {0: NaCtxId, 1: NaCtxId}, +} + +// 9.3.3.1 +// Returns ctxIdx +func CtxIdx(binIdx, maxBinIdxCtx, ctxIdxOffset int) int { + ctxIdx := NaCtxId + // table 9-39 + c, ok := ctxIdxLookup[ctxIdxOffset] + if ok { + v, ok := c[binIdx] + if ok { + return v + } + } + + switch ctxIdxOffset { + case 0: + if binIdx != 0 { + return NaCtxId + } + // 9.3.3.1.1.3 + case 3: + return 7 + case 11: + if binIdx != 0 { + return NaCtxId + } + + // 9.3.3.1.1.3 + case 14: + if binIdx > 2 { + return NaCtxId + } + case 17: + return 3 + case 21: + if binIdx < 3 { + ctxIdx = binIdx + } else { + return NaCtxId + } + case 24: + // 9.3.3.1.1.1 + case 27: + return 5 + case 32: + return 3 + case 36: + if binIdx == 0 || binIdx == 1 { + ctxIdx = binIdx + } + case 40: + fallthrough + case 47: + return 6 + case 54: + if binIdx > 1 { + ctxIdx = 5 + } + case 60: + if binIdx == 0 { + // 9.3.3.1.1.5 + } + if binIdx == 1 { + ctxIdx = 2 + } + if binIdx > 1 { + ctxIdx = 3 + } + case 64: + return NaCtxId + case 68: + if binIdx != 0 { + return NaCtxId + } + ctxIdx = 0 + case 69: + return NaCtxId + case 70: + if binIdx != 0 { + return NaCtxId + } + // 9.3.3.1.1.2 + case 77: + return NaCtxId + case 276: + if binIdx != 0 { + return NaCtxId + } + ctxIdx = 0 + case 399: + if binIdx != 0 { + return NaCtxId + } + // 9.3.3.1.1.10 + } + + return ctxIdx +} diff --git a/codec/h264/decode/cabac_test.go b/codec/h264/decode/cabac_test.go new file mode 100644 index 00000000..1c78b4a6 --- /dev/null +++ b/codec/h264/decode/cabac_test.go @@ -0,0 +1,121 @@ +package h264 + +import "testing" + +var ctxIdxTests = []struct { + binIdx int + maxBinIdxCtx int + ctxIdxOffset int + want int +}{ + {0, 0, 0, 10000}, + {999, 0, 0, 10000}, + + {0, 0, 3, 10000}, + {1, 0, 3, 276}, + {2, 0, 3, 3}, + {3, 0, 3, 4}, + {4, 0, 3, 10000}, + {5, 0, 3, 10000}, + {999, 0, 3, 7}, + + {0, 0, 11, 10000}, + {999, 0, 11, 10000}, + + {0, 0, 14, 0}, + {1, 0, 14, 1}, + {2, 0, 14, 10000}, + {999, 0, 14, 10000}, + + {0, 0, 17, 0}, + {1, 0, 17, 276}, + {2, 0, 17, 1}, + {3, 0, 17, 2}, + {4, 0, 17, 10000}, + {999, 0, 17, 3}, + + {0, 0, 21, 0}, + {1, 0, 21, 1}, + {2, 0, 21, 2}, + {999, 0, 21, 10000}, + + {0, 0, 24, 10000}, + {999, 0, 24, 10000}, + + {0, 0, 27, 10000}, + {1, 0, 27, 3}, + {2, 0, 27, 10000}, + {999, 0, 27, 5}, + + {0, 0, 32, 0}, + {1, 0, 32, 276}, + {2, 0, 32, 1}, + {3, 0, 32, 2}, + {4, 0, 32, 10000}, + {999, 0, 32, 3}, + + {0, 0, 36, 0}, + {1, 0, 36, 1}, + {2, 0, 36, 10000}, + {3, 0, 36, 3}, + {4, 0, 36, 3}, + {5, 0, 36, 3}, + + {0, 0, 40, 10000}, + + {0, 0, 47, 10000}, + {1, 0, 47, 3}, + {2, 0, 47, 4}, + {3, 0, 47, 5}, + {999, 0, 47, 6}, + + {0, 0, 54, 10000}, + {1, 0, 54, 4}, + {999, 0, 54, 5}, + + {0, 0, 60, 10000}, + {1, 0, 60, 2}, + {999, 0, 60, 3}, + + {0, 0, 64, 10000}, + {1, 0, 64, 3}, + {2, 0, 64, 3}, + {999, 0, 64, 10000}, + + {0, 0, 68, 0}, + {999, 0, 68, 10000}, + + {0, 0, 69, 0}, + {1, 0, 69, 0}, + {2, 0, 69, 0}, + + {0, 0, 70, 10000}, + {999, 0, 70, 10000}, + + {0, 0, 73, 10000}, + {1, 0, 73, 10000}, + {2, 0, 73, 10000}, + {3, 0, 73, 10000}, + {4, 0, 73, 10000}, + {999, 0, 73, 10000}, + + {0, 0, 77, 10000}, + {1, 0, 77, 10000}, + {999, 0, 77, 10000}, + + {0, 0, 276, 0}, + {999, 0, 276, 10000}, + + {0, 0, 399, 10000}, + {999, 0, 399, 10000}, +} + +// TestCtxIdx tests that the CtxIdx function returns the correct +// value given binIdx and ctxIdxOffset. +func TestCtxIdx(t *testing.T) { + for _, tt := range ctxIdxTests { + if got := CtxIdx(tt.binIdx, tt.maxBinIdxCtx, tt.ctxIdxOffset); got != tt.want { + t.Errorf("CtxIdx(%d, %d, %d) = %d, want %d", tt.binIdx, tt.maxBinIdxCtx, tt.ctxIdxOffset, got, tt.want) + } + } +} diff --git a/codec/h264/decode/frame.go b/codec/h264/decode/frame.go new file mode 100644 index 00000000..94143f53 --- /dev/null +++ b/codec/h264/decode/frame.go @@ -0,0 +1,89 @@ +package h264 + +// NALU types, as defined in table 7-1 in specifications. +const ( + naluTypeUnspecified = iota + naluTypeSliceNonIDRPicture + naluTypeSlicePartA + naluTypeSlicePartB + naluTypeSlicePartC + naluTypeSliceIDRPicture + naluTypeSEI + naluTypeSPS + naluTypePPS + naluTypeAccessUnitDelimiter + naluTypeEndOfSequence + naluTypeEndOfStream + naluTypeFillerData + naluTypeSPSExtension + naluTypePrefixNALU + naluTypeSubsetSPS + naluTypeDepthParamSet +) + +var ( + // Refer to ITU-T H.264 4/10/2017 + // Specifieds the RBSP structure in the NAL unit + NALUnitType = map[int]string{ + 0: "unspecified", + // slice_layer_without_partitioning_rbsp + 1: "coded slice of non-IDR picture", + // slice_data_partition_a_layer_rbsp + 2: "coded slice data partition a", + // slice_data_partition_b_layer_rbsp + 3: "coded slice data partition b", + // slice_data_partition_c_layer_rbsp + 4: "coded slice data partition c", + // slice_layer_without_partitioning_rbsp + 5: "coded IDR slice of picture", + // sei_rbsp + 6: "sei suppl. enhancem. info", + // seq_parameter_set_rbsp + 7: "sequence parameter set", + // pic_parameter_set_rbsp + 8: "picture parameter set", + // access_unit_delimiter_rbsp + 9: "access unit delimiter", + // end_of_seq_rbsp + 10: "end of sequence", + // end_of_stream_rbsp + 11: "end of stream", + // filler_data_rbsp + 12: "filler data", + // seq_parameter_set_extension_rbsp + 13: "sequence parameter set extensions", + // prefix_nal_unit_rbsp + 14: "prefix NAL unit", + // subset sequence parameter set + 15: "subset SPS", + // depth_parameter_set_rbsp + 16: "depth parameter set", + // 17, 18 are reserved + 17: "reserved", + 18: "reserved", + // slice_layer_without_partitioning_rbsp + 19: "coded slice of aux coded pic w/o partit.", + // slice_layer_extension_rbsp + 20: "coded slice extension", + // slice_layer_extension_rbsp + 21: "slice ext. for depth of view or 3Davc view comp.", + 22: "reserved", + 23: "reserved", + // 24 - 31 undefined + } + // ITU-T H.265 Section 7.4.1 nal_ref_idc + NALRefIDC = map[int]string{ + 0: "only nal_unit_type 6, 9, 10, 11, or 12", + 1: "anything", + 2: "anything", + 3: "anything", + 4: "anything", + } +) + +func rbspBytes(frame []byte) []byte { + if len(frame) > 8 { + return frame[8:] + } + return frame +} diff --git a/codec/h264/decode/go.mod b/codec/h264/decode/go.mod new file mode 100644 index 00000000..ffa61433 --- /dev/null +++ b/codec/h264/decode/go.mod @@ -0,0 +1,8 @@ +module github.com/ausocean/h264decode + +go 1.12 + +require ( + github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508 + github.com/pkg/errors v0.8.1 +) diff --git a/codec/h264/decode/go.sum b/codec/h264/decode/go.sum new file mode 100644 index 00000000..15c64706 --- /dev/null +++ b/codec/h264/decode/go.sum @@ -0,0 +1,4 @@ +github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508 h1:2LdkN1icT8cEFyB95fUbPE0TmQL9ZOjUv9MNJ1kg3XE= +github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508/go.mod h1:1+iKpsBoI5fsqBTrjxjM81vidVQcxXCmDrM9vc6EU2w= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/codec/h264/decode/mbType.go b/codec/h264/decode/mbType.go new file mode 100644 index 00000000..1e266446 --- /dev/null +++ b/codec/h264/decode/mbType.go @@ -0,0 +1,170 @@ +package h264 + +import ( + "errors" +) + +const MB_TYPE_INFERRED = 1000 + +var ( + ISliceMbType = map[int]string{ + 0: "I_NxN", + 1: "I_16x16_0_0_0", + 2: "I_16x16_1_0_0", + 3: "I_16x16_2_0_0", + 4: "I_16x16_3_0_0", + 5: "I_16x16_0_1_0", + 6: "I_16x16_1_1_0", + 7: "I_16x16_2_1_0", + 8: "I_16x16_3_1_0", + 9: "I_16x16_0_2_0", + 10: "I_16x16_1_2_0", + 11: "I_16x16_2_2_0", + 12: "I_16x16_3_2_0", + 13: "I_16x16_0_0_1", + 14: "I_16x16_1_0_1", + 15: "I_16x16_2_0_1", + 16: "I_16x16_3_0_1", + 17: "I_16x16_0_1_1", + 18: "I_16x16_1_1_1", + 19: "I_16x16_2_1_1", + 20: "I_16x16_3_1_1", + 21: "I_16x16_0_2_1", + 22: "I_16x16_1_2_1", + 23: "I_16x16_2_2_1", + 24: "I_16x16_3_2_1", + 25: "I_PCM", + } + SISliceMbType = map[int]string{ + 0: "SI", + } + PSliceMbType = map[int]string{ + 0: "P_L0_16x16", + 1: "P_L0_16x8", + 2: "P_L0_L0_8x16", + 3: "P_8x8", + 4: "P_8x8ref0", + MB_TYPE_INFERRED: "P_Skip", + } + BSliceMbType = map[int]string{ + 0: "B_Direct_16x16", + 1: "B_L0_16x16", + 2: "B_L1_16x16", + 3: "B_Bi_16x16", + 4: "B_L0_L0_16x8", + 5: "B_L0_L0_8x16", + 6: "B_L1_L1_16x8", + 7: "B_L1_L1_8x16", + 8: "B_L0_L1_16x8", + 9: "B_L0_L1_8x16", + 10: "B_L1_L0_16x8", + 11: "B_L1_L0_8x16", + 12: "B_L0_Bi_16x8", + 13: "B_L0_Bi_8x16", + 14: "B_L1_Bi_16x8", + 15: "B_L1_Bi_8x16", + 16: "B_Bi_L0_16x8", + 17: "B_Bi_L0_8x16", + 18: "B_Bi_l1_16x8", + 19: "B_Bi_L1_8x16", + 20: "B_Bi_Bi_16x8", + 21: "B_Bi_Bi_8x16", + 22: "B_8x8", + MB_TYPE_INFERRED: "B_Skip", + } +) + +func MbTypeName(sliceType string, mbType int) string { + sliceTypeName := "NaSliceType" + switch sliceType { + case "I": + sliceTypeName = ISliceMbType[mbType] + case "SI": + sliceTypeName = SISliceMbType[mbType] + case "P": + sliceTypeName = PSliceMbType[mbType] + case "B": + sliceTypeName = BSliceMbType[mbType] + } + return sliceTypeName +} + +// Errors used by MbPartPredMode. +var ( + errNaMode = errors.New("no mode for given slice and mb type") + errPartition = errors.New("partition must be 0") +) + +// MbPartPredMode returns a macroblock partition prediction mode for the given +// slice data, slice type, macroblock type and partition, consistent with tables +// 7-11, 7-12, 7-13 and 7-14 from the specifications. +func MbPartPredMode(data *SliceData, sliceType string, mbType, partition int) (mbPartPredMode, error) { + if partition == 0 { + switch sliceType { + case "I": + if mbType == 0 { + if data.TransformSize8x8Flag { + return intra8x8, nil + } + return intra4x4, nil + } + if mbType > 0 && mbType < 25 { + return intra16x16, nil + } + return -1, errNaMode + case "SI": + return intra4x4, nil + case "P": + fallthrough + case "SP": + if mbType >= 0 && mbType < 3 { + return predL0, nil + } else if mbType == 3 || mbType == 4 { + return -1, errNaMode + } else { + return predL0, nil + } + case "B": + switch mbType { + case 0: + return direct, nil + case 1: + fallthrough + case 4: + fallthrough + case 5: + fallthrough + case 8: + fallthrough + case 9: + fallthrough + case 12: + fallthrough + case 13: + return predL0, nil + case 2: + fallthrough + case 6: + fallthrough + case 7: + fallthrough + case 10: + fallthrough + case 11: + fallthrough + case 14: + fallthrough + case 15: + return predL1, nil + case 22: + return -1, errNaMode + default: + if mbType > 15 && mbType < 22 { + return biPred, nil + } + return direct, nil + } + } + } + return -1, errPartition +} diff --git a/codec/h264/decode/mn_vars.go b/codec/h264/decode/mn_vars.go new file mode 100644 index 00000000..3aba8d5e --- /dev/null +++ b/codec/h264/decode/mn_vars.go @@ -0,0 +1,440 @@ +package h264 + +type MN struct { + M, N int +} + +const NoCabacInitIdc = -1 + +// tables 9-12 to 9-13 +var ( + // 0-39 : MB_Type + // Maybe mapping all values in the range -128 to 128 to + // a list of tuples for input vars would be less verbose + // map[ctxIdx]MN + MNVars = map[int]map[int]MN{ + 0: {NoCabacInitIdc: {20, -15}}, + 1: {NoCabacInitIdc: {2, 54}}, + 2: {NoCabacInitIdc: {3, 74}}, + 3: {NoCabacInitIdc: {20, -15}}, + 4: {NoCabacInitIdc: {2, 54}}, + 5: {NoCabacInitIdc: {3, 74}}, + 6: {NoCabacInitIdc: {-28, 127}}, + 7: {NoCabacInitIdc: {-23, 104}}, + 8: {NoCabacInitIdc: {-6, 53}}, + 9: {NoCabacInitIdc: {-1, 54}}, + 10: {NoCabacInitIdc: {7, 51}}, + 11: { + 0: {23, 33}, + 1: {22, 25}, + 2: {29, 16}, + }, + 12: { + 0: {23, 2}, + 1: {34, 0}, + 2: {25, 0}, + }, + 13: { + 0: {21, 0}, + 1: {16, 0}, + 2: {14, 0}, + }, + 14: { + 0: {1, 9}, + 1: {-2, 9}, + 2: {-10, 51}, + }, + 15: { + 0: {0, 49}, + 1: {4, 41}, + 2: {-3, 62}, + }, + 16: { + 0: {-37, 118}, + 1: {-29, 118}, + 2: {-27, 99}, + }, + 17: { + 0: {5, 57}, + 1: {2, 65}, + 2: {26, 16}, + }, + 18: { + 0: {-13, 78}, + 1: {-6, 71}, + 2: {-4, 85}, + }, + 19: { + 0: {-11, 65}, + 1: {-13, 79}, + 2: {-24, 102}, + }, + 20: { + 0: {1, 62}, + 1: {5, 52}, + 2: {5, 57}, + }, + 21: { + 0: {12, 49}, + 1: {9, 50}, + 2: {6, 57}, + }, + 22: { + 0: {-4, 73}, + 1: {-3, 70}, + 2: {-17, 73}, + }, + 23: { + 0: {17, 50}, + 1: {10, 54}, + 2: {14, 57}, + }, + // Table 9-14 + // Should use MNSecond to get the second M value if it exists + // TODO: MNSecond determine when to provide second + 24: { + 0: {18, 64}, + 1: {26, 34}, + 2: {20, 40}, + }, + 25: { + 0: {9, 43}, + 1: {19, 22}, + 2: {20, 10}, + }, + 26: { + 0: {29, 0}, + 1: {40, 0}, + 2: {29, 0}, + }, + 27: { + 0: {26, 67}, + 1: {57, 2}, + 2: {54, 0}, + }, + 28: { + 0: {16, 90}, + 1: {41, 36}, + 2: {37, 42}, + }, + 29: { + 0: {9, 104}, + 1: {26, 59}, + 2: {12, 97}, + }, + 30: { + 0: {-4, 127}, // Second M: 6 + 1: {-4, 127}, // Second M: 5 + 2: {-3, 127}, // Second M: 2 + }, + 31: { + 0: {-2, 104}, // Second M: 0 + 1: {-1, 101}, // Second M: 5 + 2: {-2, 117}, // Second M: 2 + }, + 32: { + 0: {1, 67}, + 1: {-4, 76}, + 2: {-2, 74}, + }, + 33: { + 0: {-1, 78}, // Second M: 3 + 1: {-6, 71}, + 2: {-4, 85}, + }, + 34: { + 0: {-1, 65}, // Second M: 1 + 1: {-1, 79}, // Second M: 3 + 2: {-2, 102}, // Second M: 4 + }, + 35: { + 0: {1, 62}, + 1: {5, 52}, + 2: {5, 57}, + }, + 36: { + 0: {-6, 86}, + 1: {6, 69}, + 2: {-6, 93}, + }, + 37: { + 0: {-1, 95}, // Second M: 7 + 1: {-1, 90}, // Second M: 3 + 2: {-1, 88}, // Second M: 4 + }, + 38: { + 0: {-6, 61}, + 1: {0, 52}, + 2: {-6, 44}, + }, + 39: { + 0: {9, 45}, + 1: {8, 43}, + 2: {4, 55}, + }, + } +) + +// TODO: MNSecond determine when to provide second +func MNSecond(ctxIdx, cabacInitIdc int) {} + +// Table 9-18 +// Coded block pattern (luma y chroma) +// map[ctxIdx][cabacInitIdc]MN +func CodedblockPatternMN(ctxIdx, cabacInitIdc int, sliceType string) MN { + var mn MN + if sliceType != "I" && sliceType != "SI" { + logger.Printf("warning: trying to initialize %s slice type\n", sliceType) + } + switch ctxIdx { + case 70: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {0, 45}, {13, 15}, {7, 34}, + }[cabacInitIdc] + } + return MN{0, 11} + case 71: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-4, 78}, {7, 51}, {-9, 88}, + }[cabacInitIdc] + } + return MN{1, 55} + case 72: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-3, 96}, {2, 80}, {-20, 127}, + }[cabacInitIdc] + } + return MN{0, 69} + case 73: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-27, 126}, {-39, 127}, {-36, 127}, + }[cabacInitIdc] + } + return MN{-17, 127} + case 74: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-28, 98}, {-18, 91}, {-17, 91}, + }[cabacInitIdc] + } + return MN{-13, 102} + case 75: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-25, 101}, {-17, 96}, {-14, 95}, + }[cabacInitIdc] + } + return MN{0, 82} + case 76: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-23, 67}, {-26, 81}, {-25, 84}, + }[cabacInitIdc] + } + return MN{-7, 24} + case 77: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-28, 82}, {-35, 98}, {-25, 86}, + }[cabacInitIdc] + } + return MN{-21, 107} + case 78: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-20, 94}, {-24, 102}, {-12, 89}, + }[cabacInitIdc] + } + return MN{-27, 127} + case 79: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-16, 83}, {-23, 97}, {-17, 91}, + }[cabacInitIdc] + } + return MN{-31, 127} + case 80: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-22, 110}, {-27, 119}, {-31, 127}, + }[cabacInitIdc] + } + return MN{-24, 127} + case 81: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-21, 91}, {-24, 99}, {-14, 76}, + }[cabacInitIdc] + } + return MN{-18, 95} + case 82: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-18, 102}, {-21, 110}, {-18, 103}, + }[cabacInitIdc] + } + return MN{-27, 127} + case 83: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-13, 93}, {-18, 102}, {-13, 90}, + }[cabacInitIdc] + } + return MN{-21, 114} + case 84: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-29, 127}, {-36, 127}, {-37, 127}, + }[cabacInitIdc] + } + return MN{-30, 127} + case 85: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-7, 92}, {0, 80}, {11, 80}, + }[cabacInitIdc] + } + return MN{-17, 123} + case 86: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-5, 89}, {-5, 89}, {5, 76}, + }[cabacInitIdc] + } + return MN{-12, 115} + case 87: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-7, 96}, {-7, 94}, {2, 84}, + }[cabacInitIdc] + } + return MN{-16, 122} + // TODO: 88 to 104 + case 88: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-13, 108}, {-4, 92}, {5, 78}, + }[cabacInitIdc] + } + return MN{-11, 115} + case 89: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-3, 46}, {0, 39}, {-6, 55}, + }[cabacInitIdc] + } + return MN{-12, 63} + case 90: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-1, 65}, {0, 65}, {4, 61}, + }[cabacInitIdc] + } + return MN{-2, 68} + case 91: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-1, 57}, {-15, 84}, {-14, 83}, + }[cabacInitIdc] + } + return MN{-15, 85} + case 92: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-9, 93}, {-36, 127}, {-37, 127}, + }[cabacInitIdc] + } + return MN{-13, 104} + case 93: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-3, 74}, {-2, 73}, {-5, 79}, + }[cabacInitIdc] + } + return MN{-3, 70} + case 94: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-9, 92}, {-12, 104}, {-11, 104}, + }[cabacInitIdc] + } + return MN{-8, 93} + case 95: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-8, 87}, {-9, 91}, {-11, 91}, + }[cabacInitIdc] + } + return MN{-10, 90} + case 96: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-23, 126}, {-31, 127}, {-30, 127}, + }[cabacInitIdc] + } + return MN{-30, 127} + case 97: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {5, 54}, {3, 55}, {0, 65}, + }[cabacInitIdc] + } + return MN{-1, 74} + case 98: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {6, 60}, {7, 56}, {-2, 79}, + }[cabacInitIdc] + } + return MN{-6, 97} + case 99: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {6, 59}, {7, 55}, {0, 72}, + }[cabacInitIdc] + } + return MN{-7, 91} + case 100: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {6, 69}, {8, 61}, {-4, 92}, + }[cabacInitIdc] + } + return MN{-20, 127} + case 101: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-1, 48}, {-3, 53}, {-6, 56}, + }[cabacInitIdc] + } + return MN{-4, 56} + case 102: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {0, 68}, {0, 68}, {3, 68}, + }[cabacInitIdc] + } + return MN{-5, 82} + case 103: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-4, 69}, {-7, 74}, {-8, 71}, + }[cabacInitIdc] + } + return MN{-7, 76} + case 104: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-8, 88}, {-9, 88}, {-13, 98}, + }[cabacInitIdc] + } + return MN{-22, 125} + + } + + return mn +} diff --git a/codec/h264/decode/nalUnit.go b/codec/h264/decode/nalUnit.go new file mode 100644 index 00000000..35eeab76 --- /dev/null +++ b/codec/h264/decode/nalUnit.go @@ -0,0 +1,161 @@ +package h264 + +import ( + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +type NalUnit struct { + NumBytes int + ForbiddenZeroBit int + RefIdc int + Type int + SvcExtensionFlag int + Avc3dExtensionFlag int + IdrFlag int + PriorityId int + NoInterLayerPredFlag int + DependencyId int + QualityId int + TemporalId int + UseRefBasePicFlag int + DiscardableFlag int + OutputFlag int + ReservedThree2Bits int + HeaderBytes int + NonIdrFlag int + ViewId int + AnchorPicFlag int + InterViewFlag int + ReservedOneBit int + ViewIdx int + DepthFlag int + EmulationPreventionThreeByte byte + rbsp []byte +} + +func NalUnitHeaderSvcExtension(nalUnit *NalUnit, br *bits.BitReader) error { + return readFields(br, []field{ + {&nalUnit.IdrFlag, "IdrFlag", 1}, + {&nalUnit.PriorityId, "PriorityId", 6}, + {&nalUnit.NoInterLayerPredFlag, "NoInterLayerPredFlag", 1}, + {&nalUnit.DependencyId, "DependencyId", 3}, + {&nalUnit.QualityId, "QualityId", 4}, + {&nalUnit.TemporalId, "TemporalId", 3}, + {&nalUnit.UseRefBasePicFlag, "UseRefBasePicFlag", 1}, + {&nalUnit.DiscardableFlag, "DiscardableFlag", 1}, + {&nalUnit.OutputFlag, "OutputFlag", 1}, + {&nalUnit.ReservedThree2Bits, "ReservedThree2Bits", 2}, + }) +} + +func NalUnitHeader3davcExtension(nalUnit *NalUnit, br *bits.BitReader) error { + return readFields(br, []field{ + {&nalUnit.ViewIdx, "ViewIdx", 8}, + {&nalUnit.DepthFlag, "DepthFlag", 1}, + {&nalUnit.NonIdrFlag, "NonIdrFlag", 1}, + {&nalUnit.TemporalId, "TemporalId", 3}, + {&nalUnit.AnchorPicFlag, "AnchorPicFlag", 1}, + {&nalUnit.InterViewFlag, "InterViewFlag", 1}, + }) +} + +func NalUnitHeaderMvcExtension(nalUnit *NalUnit, br *bits.BitReader) error { + return readFields(br, []field{ + {&nalUnit.NonIdrFlag, "NonIdrFlag", 1}, + {&nalUnit.PriorityId, "PriorityId", 6}, + {&nalUnit.ViewId, "ViewId", 10}, + {&nalUnit.TemporalId, "TemporalId", 3}, + {&nalUnit.AnchorPicFlag, "AnchorPicFlag", 1}, + {&nalUnit.InterViewFlag, "InterViewFlag", 1}, + {&nalUnit.ReservedOneBit, "ReservedOneBit", 1}, + }) +} + +func (n *NalUnit) RBSP() []byte { + return n.rbsp +} + +func NewNalUnit(frame []byte, numBytesInNal int) (*NalUnit, error) { + logger.Printf("debug: reading %d byte NAL\n", numBytesInNal) + nalUnit := NalUnit{ + NumBytes: numBytesInNal, + HeaderBytes: 1, + } + // TODO: pass in actual io.Reader to NewBitReader + br := bits.NewBitReader(nil) + + err := readFields(br, []field{ + {&nalUnit.ForbiddenZeroBit, "ForbiddenZeroBit", 1}, + {&nalUnit.RefIdc, "NalRefIdc", 2}, + {&nalUnit.Type, "NalUnitType", 5}, + }) + if err != nil { + return nil, err + } + + if nalUnit.Type == 14 || nalUnit.Type == 20 || nalUnit.Type == 21 { + if nalUnit.Type != 21 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SvcExtensionFlag") + } + nalUnit.SvcExtensionFlag = int(b) + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read Avc3dExtensionFlag") + } + nalUnit.Avc3dExtensionFlag = int(b) + } + if nalUnit.SvcExtensionFlag == 1 { + NalUnitHeaderSvcExtension(&nalUnit, br) + nalUnit.HeaderBytes += 3 + } else if nalUnit.Avc3dExtensionFlag == 1 { + NalUnitHeader3davcExtension(&nalUnit, br) + nalUnit.HeaderBytes += 2 + } else { + NalUnitHeaderMvcExtension(&nalUnit, br) + nalUnit.HeaderBytes += 3 + + } + } + + logger.Printf("debug: found %d byte header. Reading body\n", nalUnit.HeaderBytes) + for i := nalUnit.HeaderBytes; i < nalUnit.NumBytes; i++ { + next3Bytes, err := br.PeekBits(24) + if err != nil { + logger.Printf("error: while reading next 3 NAL bytes: %v\n", err) + break + } + // Little odd, the err above and the i+2 check might be synonyms + if i+2 < nalUnit.NumBytes && next3Bytes == 0x000003 { + for j := 0; j < 2; j++ { + rbspByte, err := br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read rbspByte") + } + nalUnit.rbsp = append(nalUnit.rbsp, byte(rbspByte)) + } + i += 2 + + // Read Emulation prevention three byte. + eptByte, err := br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read eptByte") + } + nalUnit.EmulationPreventionThreeByte = byte(eptByte) + } else { + if b, err := br.ReadBits(8); err == nil { + nalUnit.rbsp = append(nalUnit.rbsp, byte(b)) + } else { + logger.Printf("error: while reading byte %d of %d nal bytes: %v\n", i, nalUnit.NumBytes, err) + break + } + } + } + + // nalUnit.rbsp = frame[nalUnit.HeaderBytes:] + logger.Printf("info: decoded %s NAL with %d RBSP bytes\n", NALUnitType[nalUnit.Type], len(nalUnit.rbsp)) + return &nalUnit, nil +} diff --git a/codec/h264/decode/parse.go b/codec/h264/decode/parse.go new file mode 100644 index 00000000..18245f3e --- /dev/null +++ b/codec/h264/decode/parse.go @@ -0,0 +1,161 @@ +/* +NAME + parse.go + +DESCRIPTION + parse.go provides parsing processes for syntax elements of different + descriptors specified in 7.2 of ITU-T H.264. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + mrmod +*/ + +package h264 + +import ( + "math" + + "github.com/icza/bitio" + "github.com/pkg/errors" +) + +type mbPartPredMode int8 + +const ( + intra4x4 mbPartPredMode = iota + intra8x8 + intra16x16 + predL0 + predL1 + direct + biPred + inter +) + +// readUe parses a syntax element of ue(v) descriptor, i.e. an unsigned integer +// Exp-Golomb-coded element using method as specified in section 9.1 of ITU-T H.264. +// +// TODO: this should return uint, but rest of code needs to be changed for this +// to happen. +func readUe(r bitio.Reader) (int, error) { + nZeros := -1 + var err error + for b := uint64(0); b == 0; nZeros++ { + b, err = r.ReadBits(1) + if err != nil { + return 0, err + } + } + rem, err := r.ReadBits(byte(nZeros)) + if err != nil { + return 0, err + } + return int(math.Pow(float64(2), float64(nZeros)) - 1 + float64(rem)), nil +} + +// readTe parses a syntax element of te(v) descriptor i.e, truncated +// Exp-Golomb-coded syntax element using method as specified in section 9.1 +// Rec. ITU-T H.264 (04/2017). +// +// TODO: this should also return uint. +func readTe(r bitio.Reader, x uint) (int, error) { + if x > 1 { + return readUe(r) + } + + if x == 1 { + b, err := r.ReadBits(1) + if err != nil { + return 0, errors.Wrap(err, "could not read bit") + } + if b == 0 { + return 1, nil + } + return 0, nil + } + + return 0, errReadTeBadX +} + +var errReadTeBadX = errors.New("x must be more than or equal to 1") + +// readSe parses a syntax element with descriptor se(v), i.e. a signed integer +// Exp-Golomb-coded syntax element, using the method described in sections +// 9.1 and 9.1.1 in Rec. ITU-T H.264 (04/2017). +func readSe(r bitio.Reader) (int, error) { + codeNum, err := readUe(r) + if err != nil { + return 0, errors.Wrap(err, "error reading ue(v)") + } + + return int(math.Pow(-1, float64(codeNum+1)) * math.Ceil(float64(codeNum)/2.0)), nil +} + +// readMe parses a syntax element of me(v) descriptor, i.e. mapped +// Exp-Golomb-coded element, using methods described in sections 9.1 and 9.1.2 +// in Rec. ITU-T H.264 (04/2017). +func readMe(r bitio.Reader, chromaArrayType uint, mpm mbPartPredMode) (uint, error) { + // Indexes to codedBlockPattern map. + var i1, i2, i3 int + + // ChromaArrayType selects first index. + switch chromaArrayType { + case 1, 2: + i1 = 0 + case 0, 3: + i1 = 1 + default: + return 0, errInvalidCAT + } + + // CodeNum from readUe selects second index. + i2, err := readUe(r) + if err != nil { + return 0, errors.Wrap(err, "error from readUe") + } + + // Need to check that we won't go out of bounds with this index. + if i2 >= len(codedBlockPattern[i1]) { + return 0, errInvalidCodeNum + } + + // Macroblock prediction mode selects third index. + switch mpm { + case intra4x4, intra8x8: + i3 = 0 + case inter: + i3 = 1 + default: + return 0, errInvalidMPM + } + + return codedBlockPattern[i1][i2][i3], nil +} + +// Errors used by readMe. +var ( + errInvalidCodeNum = errors.New("invalid codeNum") + errInvalidMPM = errors.New("invalid macroblock prediction mode") + errInvalidCAT = errors.New("invalid chroma array type") +) + +// codedBlockPattern contains data from table 9-4 in ITU-T H.264 (04/2017) +// for mapping a chromaArrayType, codeNum and macroblock prediction mode to a +// coded block pattern. +var codedBlockPattern = [][][2]uint{ + // Table 9-4 (a) for ChromaArrayType = 1 or 2 + { + {47, 0}, {31, 16}, {15, 1}, {0, 2}, {23, 4}, {27, 8}, {29, 32}, {30, 3}, + {7, 5}, {11, 10}, {13, 12}, {14, 15}, {39, 47}, {43, 7}, {45, 11}, {46, 13}, + {16, 14}, {3, 6}, {31, 9}, {10, 31}, {12, 35}, {19, 37}, {21, 42}, {26, 44}, + {28, 33}, {35, 34}, {37, 36}, {42, 40}, {44, 39}, {1, 43}, {2, 45}, {4, 46}, + {8, 17}, {17, 18}, {18, 20}, {20, 24}, {24, 19}, {6, 21}, {9, 26}, {22, 28}, + {25, 23}, {32, 27}, {33, 29}, {34, 30}, {36, 22}, {40, 25}, {38, 38}, {41, 41}, + }, + // Table 9-4 (b) for ChromaArrayType = 0 or 3 + { + {15, 0}, {0, 1}, {7, 2}, {11, 4}, {13, 8}, {14, 3}, {3, 5}, {5, 10}, {10, 12}, + {12, 15}, {1, 7}, {2, 11}, {4, 13}, {8, 14}, {6, 6}, {9, 9}, + }, +} diff --git a/codec/h264/decode/parse_test.go b/codec/h264/decode/parse_test.go new file mode 100644 index 00000000..4356fa50 --- /dev/null +++ b/codec/h264/decode/parse_test.go @@ -0,0 +1,154 @@ +/* +NAME + parse_test.go + +DESCRIPTION + parse_test.go provides testing for parsing utilities provided in parse.go + +AUTHOR + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) +*/ + +package h264 + +import ( + "bytes" + "testing" + + "github.com/icza/bitio" +) + +// TestReadUe checks that readUe correctly parses an Exp-Golomb-coded element +// to a code number. +func TestReadUe(t *testing.T) { + // tests has been derived from Table 9-2 in ITU-T H.H264, showing bit strings + // and corresponding codeNums. + tests := []struct { + in []byte // The bitstring we wish to read. + want uint // The expected codeNum. + }{ + {[]byte{0x80}, 0}, // Bit string: 1, codeNum: 0 + {[]byte{0x40}, 1}, // Bit string: 010, codeNum: 1 + {[]byte{0x60}, 2}, // Bit string: 011, codeNum: 2 + {[]byte{0x20}, 3}, // Bit string: 00100, codeNum: 3 + {[]byte{0x28}, 4}, // Bit string: 00101, codeNum: 4 + {[]byte{0x30}, 5}, // Bit string: 00110, codeNum: 5 + {[]byte{0x38}, 6}, // Bit string: 00111, codeNum: 6 + {[]byte{0x10}, 7}, // Bit string: 0001000, codeNum: 7 + {[]byte{0x12}, 8}, // Bit string: 0001001, codeNum: 8 + {[]byte{0x14}, 9}, // Bit string: 0001010, codeNum: 9 + {[]byte{0x16}, 10}, // Bit string: 0001011, codeNum: 10 + } + + for i, test := range tests { + got, err := readUe(bitio.NewReader(bytes.NewReader(test.in))) + if err != nil { + t.Fatalf("did not expect error: %v from readUe", err) + } + + if test.want != uint(got) { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +// TestReadTe checks that readTe correctly parses a truncated Exp-Golomb-coded +// syntax element. Expected results are outlined in section 9.1 pg209 Rec ITU-T +// H.264 (04/2017) +func TestReadTe(t *testing.T) { + tests := []struct { + in []byte // The bitstring we will read. + x uint // The upper bound of the range. + want uint // Expected result from readTe. + err error // Expected error from readTe. + }{ + {[]byte{0x30}, 1, 1, nil}, + {[]byte{0x80}, 1, 0, nil}, + {[]byte{0x30}, 5, 5, nil}, + {[]byte{0x30}, 0, 0, errReadTeBadX}, + } + + for i, test := range tests { + got, err := readTe(bitio.NewReader(bytes.NewReader(test.in)), test.x) + if err != test.err { + t.Fatalf("did not get expected error for test: %v\nGot: %v\nWant: %v\n", i, err, test.err) + } + + if test.want != uint(got) { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +// TestReadSe checks that readSe correctly parses an se(v) signed integer +// Exp-Golomb-coded syntax element. Expected behaviour is found in section 9.1 +// and 9.1.1 of the Rec. ITU-T H.264(04/2017). +func TestReadSe(t *testing.T) { + // tests has been derived from table 9-3 of the specifications. + tests := []struct { + in []byte // Bitstring to read. + want int // Expected value from se(v) parsing process. + }{ + {[]byte{0x80}, 0}, + {[]byte{0x40}, 1}, + {[]byte{0x60}, -1}, + {[]byte{0x20}, 2}, + {[]byte{0x28}, -2}, + {[]byte{0x30}, 3}, + {[]byte{0x38}, -3}, + } + + for i, test := range tests { + got, err := readSe(bitio.NewReader(bytes.NewReader(test.in))) + if err != nil { + t.Fatalf("did not expect error: %v from readSe", err) + } + + if test.want != got { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +// TestReadMe checks that readMe correctly parses a me(v) mapped +// Exp-Golomb-coded element. Expected behaviour is described in in sections 9.1 +// and 9.1.2 in Rec. ITU-T H.264 (04/2017). +func TestReadMe(t *testing.T) { + in := []byte{0x38} // Bit string: 00111, codeNum: 6. + inErr := []byte{0x07, 0xe0} // Bit string: 0000 0111 111, codeNum: 62 (will give invalid codeNum err) + + tests := []struct { + in []byte // Input data. + cat uint // Chroma array.. + mpm mbPartPredMode + want uint // Expected result from readMe. + err error // Expected value of err from readMe. + }{ + {in, 1, intra4x4, 29, nil}, + {in, 1, intra8x8, 29, nil}, + {in, 1, inter, 32, nil}, + {in, 2, intra4x4, 29, nil}, + {in, 2, intra8x8, 29, nil}, + {in, 2, inter, 32, nil}, + {in, 0, intra4x4, 3, nil}, + {in, 0, intra8x8, 3, nil}, + {in, 0, inter, 5, nil}, + {in, 3, intra4x4, 3, nil}, + {in, 3, intra8x8, 3, nil}, + {in, 3, inter, 5, nil}, + {inErr, 1, intra4x4, 0, errInvalidCodeNum}, + {in, 4, intra4x4, 0, errInvalidCAT}, + {in, 0, 4, 0, errInvalidMPM}, + } + + for i, test := range tests { + got, err := readMe(bitio.NewReader(bytes.NewReader(test.in)), test.cat, test.mpm) + if err != test.err { + t.Fatalf("did not expect to get error: %v for test: %v", err, i) + } + + if test.want != got { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} diff --git a/codec/h264/decode/pps.go b/codec/h264/decode/pps.go new file mode 100644 index 00000000..ebc3d45f --- /dev/null +++ b/codec/h264/decode/pps.go @@ -0,0 +1,239 @@ +package h264 + +import ( + "math" + + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +// import "strings" + +// Specification Page 46 7.3.2.2 + +type PPS struct { + ID, SPSID int + EntropyCodingMode int + NumSliceGroupsMinus1 int + BottomFieldPicOrderInFramePresent bool + NumSlicGroupsMinus1 int + SliceGroupMapType int + RunLengthMinus1 []int + TopLeft []int + BottomRight []int + SliceGroupChangeDirection bool + SliceGroupChangeRateMinus1 int + PicSizeInMapUnitsMinus1 int + SliceGroupId []int + NumRefIdxL0DefaultActiveMinus1 int + NumRefIdxL1DefaultActiveMinus1 int + WeightedPred bool + WeightedBipred int + PicInitQpMinus26 int + PicInitQsMinus26 int + ChromaQpIndexOffset int + DeblockingFilterControlPresent bool + ConstrainedIntraPred bool + RedundantPicCntPresent bool + Transform8x8Mode int + PicScalingMatrixPresent bool + PicScalingListPresent []bool + SecondChromaQpIndexOffset int +} + +func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { + logger.Printf("debug: PPS RBSP %d bytes %d bits == \n", len(rbsp), len(rbsp)*8) + logger.Printf("debug: \t%#v\n", rbsp[0:8]) + pps := PPS{} + // TODO: give this io.Reader + br := bits.NewBitReader(nil) + + var err error + pps.ID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ID") + } + + pps.SPSID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SPS ID") + } + + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read EntropyCodingMode") + } + pps.EntropyCodingMode = int(b) + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read BottomFieldPicOrderInFramePresent") + } + pps.BottomFieldPicOrderInFramePresent = b == 1 + + pps.NumSliceGroupsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse NumSliceGroupsMinus1") + } + + if pps.NumSliceGroupsMinus1 > 0 { + pps.SliceGroupMapType, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceGroupMapType") + } + + if pps.SliceGroupMapType == 0 { + for iGroup := 0; iGroup <= pps.NumSliceGroupsMinus1; iGroup++ { + pps.RunLengthMinus1[iGroup], err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse RunLengthMinus1") + } + } + } else if pps.SliceGroupMapType == 2 { + for iGroup := 0; iGroup < pps.NumSliceGroupsMinus1; iGroup++ { + pps.TopLeft[iGroup], err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse TopLeft[iGroup]") + } + if err != nil { + return nil, errors.Wrap(err, "could not parse TopLeft[iGroup]") + } + + pps.BottomRight[iGroup], err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse BottomRight[iGroup]") + } + } + } else if pps.SliceGroupMapType > 2 && pps.SliceGroupMapType < 6 { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SliceGroupChangeDirection") + } + pps.SliceGroupChangeDirection = b == 1 + + pps.SliceGroupChangeRateMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceGroupChangeRateMinus1") + } + } else if pps.SliceGroupMapType == 6 { + pps.PicSizeInMapUnitsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PicSizeInMapUnitsMinus1") + } + + for i := 0; i <= pps.PicSizeInMapUnitsMinus1; i++ { + b, err = br.ReadBits(int(math.Ceil(math.Log2(float64(pps.NumSliceGroupsMinus1 + 1))))) + if err != nil { + return nil, errors.Wrap(err, "coult not read SliceGroupId") + } + pps.SliceGroupId[i] = int(b) + } + } + + } + pps.NumRefIdxL0DefaultActiveMinus1, err = readUe(nil) + if err != nil { + return nil, errors.New("could not parse NumRefIdxL0DefaultActiveMinus1") + } + + pps.NumRefIdxL1DefaultActiveMinus1, err = readUe(nil) + if err != nil { + return nil, errors.New("could not parse NumRefIdxL1DefaultActiveMinus1") + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read WeightedPred") + } + pps.WeightedPred = b == 1 + + b, err = br.ReadBits(2) + if err != nil { + return nil, errors.Wrap(err, "could not read WeightedBipred") + } + pps.WeightedBipred = int(b) + + pps.PicInitQpMinus26, err = readSe(nil) + if err != nil { + return nil, errors.New("could not parse PicInitQpMinus26") + } + + pps.PicInitQsMinus26, err = readSe(nil) + if err != nil { + return nil, errors.New("could not parse PicInitQsMinus26") + } + + pps.ChromaQpIndexOffset, err = readSe(nil) + if err != nil { + return nil, errors.New("could not parse ChromaQpIndexOffset") + } + + err = readFlags(br, []flag{ + {&pps.DeblockingFilterControlPresent, "DeblockingFilterControlPresent"}, + {&pps.ConstrainedIntraPred, "ConstrainedIntraPred"}, + {&pps.RedundantPicCntPresent, "RedundantPicCntPresent"}, + }) + if err != nil { + return nil, err + } + + logger.Printf("debug: \tChecking for more PPS data") + if moreRBSPData(br) { + logger.Printf("debug: \tProcessing additional PPS data") + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read Transform8x8Mode") + } + pps.Transform8x8Mode = int(b) + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read PicScalingMatrixPresent") + } + pps.PicScalingMatrixPresent = b == 1 + + if pps.PicScalingMatrixPresent { + v := 6 + if sps.ChromaFormat != chroma444 { + v = 2 + } + for i := 0; i < 6+(v*pps.Transform8x8Mode); i++ { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read PicScalingListPresent") + } + pps.PicScalingListPresent[i] = b == 1 + if pps.PicScalingListPresent[i] { + if i < 6 { + scalingList( + br, + ScalingList4x4[i], + 16, + DefaultScalingMatrix4x4[i]) + + } else { + scalingList( + br, + ScalingList8x8[i], + 64, + DefaultScalingMatrix8x8[i-6]) + + } + } + } + pps.SecondChromaQpIndexOffset, err = readSe(nil) + if err != nil { + return nil, errors.New("could not parse SecondChromaQpIndexOffset") + } + } + moreRBSPData(br) + // rbspTrailingBits() + } + + if showPacket { + debugPacket("PPS", pps) + } + return &pps, nil + +} diff --git a/codec/h264/decode/rangeTabLPS.go b/codec/h264/decode/rangeTabLPS.go new file mode 100644 index 00000000..3bcfb000 --- /dev/null +++ b/codec/h264/decode/rangeTabLPS.go @@ -0,0 +1,100 @@ +package h264 + +import "errors" + +// Number of columns and rows for rangeTabLPS. +const ( + rangeTabLPSColumns = 4 + rangeTabLPSRows = 64 +) + +// rangeTabLPS provides values of codIRangeLPS as defined in section 9.3.3.2.1.1, +// tab 9-44. Rows correspond to pStateIdx, and columns to qCodIRangeIdx, i.e. +// codIRangeLPS = rangeTabLPS[pStateIdx][qCodIRangeIdx]. +var rangeTabLPS = [rangeTabLPSRows][rangeTabLPSColumns]int{ + 0: {128, 176, 208, 240}, + 1: {128, 167, 197, 227}, + 2: {128, 158, 187, 216}, + 3: {123, 150, 178, 205}, + 4: {116, 142, 169, 195}, + 5: {111, 135, 160, 185}, + 6: {105, 128, 152, 175}, + 7: {100, 122, 144, 166}, + 8: {95, 116, 137, 158}, + 9: {90, 110, 130, 150}, + 10: {85, 104, 123, 142}, + 11: {81, 99, 117, 135}, + 12: {77, 94, 111, 128}, + 13: {73, 89, 105, 122}, + 14: {69, 85, 100, 116}, + 15: {66, 80, 95, 110}, + 16: {62, 76, 90, 104}, + 17: {59, 72, 86, 99}, + 18: {56, 69, 81, 94}, + 19: {53, 65, 77, 89}, + 20: {51, 62, 73, 85}, + 21: {48, 59, 69, 80}, + 22: {46, 56, 66, 76}, + 23: {43, 53, 63, 72}, + 24: {41, 50, 59, 69}, + 25: {39, 48, 56, 65}, + 26: {37, 45, 54, 62}, + 27: {35, 43, 51, 59}, + 28: {33, 41, 48, 56}, + 29: {32, 39, 46, 53}, + 30: {30, 37, 43, 50}, + 31: {29, 35, 41, 48}, + 32: {27, 33, 39, 45}, + 33: {26, 61, 67, 43}, + 34: {24, 30, 35, 41}, + 35: {23, 28, 33, 39}, + 36: {22, 27, 32, 37}, + 37: {21, 26, 30, 35}, + 38: {20, 24, 29, 33}, + 39: {19, 23, 27, 31}, + 40: {18, 22, 26, 30}, + 41: {17, 21, 25, 28}, + 42: {16, 20, 23, 27}, + 43: {15, 19, 22, 25}, + 44: {14, 18, 21, 24}, + 45: {14, 17, 20, 23}, + 46: {13, 16, 19, 22}, + 47: {12, 15, 18, 21}, + 48: {12, 14, 17, 20}, + 49: {11, 14, 16, 19}, + 50: {11, 13, 15, 18}, + 51: {10, 12, 15, 17}, + 52: {10, 12, 14, 16}, + 53: {9, 11, 13, 15}, + 54: {9, 11, 12, 14}, + 55: {8, 10, 12, 14}, + 56: {8, 9, 11, 13}, + 57: {7, 9, 11, 12}, + 58: {7, 9, 10, 12}, + 59: {7, 8, 10, 11}, + 60: {6, 8, 9, 11}, + 61: {6, 7, 9, 10}, + 62: {6, 7, 8, 9}, + 63: {2, 2, 2, 2}, +} + +// Errors returnable by retCodIRangeLPS. +var ( + errPStateIdx = errors.New("invalid pStateIdx") + errQCodIRangeIdx = errors.New("invalid qCodIRangeIdx") +) + +// retCodIRangeLPS retrieves the codIRangeLPS for a given pStateIdx and +// qCodIRangeIdx using the rangeTabLPS as specified in section 9.3.3.2.1.1, +// tab 9-44. +func retCodIRangeLPS(pStateIdx, qCodIRangeIdx int) (int, error) { + if pStateIdx < 0 || rangeTabLPSRows <= pStateIdx { + return 0, errPStateIdx + } + + if qCodIRangeIdx < 0 || rangeTabLPSColumns <= qCodIRangeIdx { + return 0, errQCodIRangeIdx + } + + return rangeTabLPS[pStateIdx][qCodIRangeIdx], nil +} diff --git a/codec/h264/decode/rbsp.go b/codec/h264/decode/rbsp.go new file mode 100644 index 00000000..57826439 --- /dev/null +++ b/codec/h264/decode/rbsp.go @@ -0,0 +1,86 @@ +package h264 + +import ( + "fmt" + + "github.com/ausocean/h264decode/h264/bits" +) + +const ( + profileBaseline = 66 + profileMain = 77 + profileExtended = 88 + profileHigh = 100 + profileHigh10 = 110 + profileHigh422 = 122 + profileHigh444Predictive = 244 +) + +var ( + ProfileIDC = map[int]string{ + profileBaseline: "Baseline", + profileMain: "Main", + profileExtended: "Extended", + profileHigh: "High", + profileHigh10: "High 10", + profileHigh422: "High 4:2:2", + profileHigh444Predictive: "High 4:4:4", + } +) + +// 7.3.2.11 +func rbspTrailingBits(br *bits.BitReader) { + _, err := br.ReadBits(1) + if err != nil { + fmt.Printf("error reading StopOneBit: %v\n", err) + } + // 7.2 + for !br.ByteAligned() { + // RBSPAlignmentZeroBit + _, err := br.ReadBits(1) + if err != nil { + fmt.Printf("error reading AligntmentZeroBit: %v\n", err) + break + } + } +} +func NewRBSP(frame []byte) []byte { + // TODO: NALUType 14,20,21 add padding to 3rd or 4th byte + return frame[5:] +} + +// TODO: Should be base-ten big endian bit arrays, not bytes +// ITU A.2.1.1 - Bit 9 is 1 +func isConstrainedBaselineProfile(profile int, b []byte) bool { + if profile != profileBaseline { + return false + } + if len(b) > 8 && b[8] == 1 { + return true + } + return false +} + +// ITU A2.4.2 - Bit 12 and 13 are 1 +func isConstrainedHighProfile(profile int, b []byte) bool { + if profile != profileHigh { + return false + } + if len(b) > 13 { + if b[12] == 1 && b[13] == 1 { + return true + } + } + return false +} + +// ITU A2.8 - Bit 11 is 1 +func isHigh10IntraProfile(profile int, b []byte) bool { + if profile != profileHigh10 { + return false + } + if len(b) > 11 && b[11] == 1 { + return true + } + return false +} diff --git a/codec/h264/decode/read.go b/codec/h264/decode/read.go new file mode 100644 index 00000000..c50180dd --- /dev/null +++ b/codec/h264/decode/read.go @@ -0,0 +1,230 @@ +package h264 + +import ( + "fmt" + "io" + "os" + + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +type H264Reader struct { + IsStarted bool + Stream io.Reader + NalUnits []*bits.BitReader + VideoStreams []*VideoStream + DebugFile *os.File + bytes []byte + byteOffset int + *bits.BitReader +} + +func (h *H264Reader) BufferToReader(cntBytes int) error { + buf := make([]byte, cntBytes) + if _, err := h.Stream.Read(buf); err != nil { + logger.Printf("error: while reading %d bytes: %v\n", cntBytes, err) + return err + } + h.bytes = append(h.bytes, buf...) + if h.DebugFile != nil { + h.DebugFile.Write(buf) + } + h.byteOffset += cntBytes + return nil +} + +func (h *H264Reader) Discard(cntBytes int) error { + buf := make([]byte, cntBytes) + if _, err := h.Stream.Read(buf); err != nil { + logger.Printf("error: while discarding %d bytes: %v\n", cntBytes, err) + return err + } + h.byteOffset += cntBytes + return nil +} + +// TODO: what does this do ? +func bitVal(bits []int) int { + t := 0 + for i, b := range bits { + if b == 1 { + t += 1 << uint((len(bits)-1)-i) + } + } + // fmt.Printf("\t bitVal: %d\n", t) + return t +} + +func (h *H264Reader) Start() { + for { + // TODO: need to handle error from this. + nalUnit, _, _ := h.readNalUnit() + switch nalUnit.Type { + case naluTypeSPS: + // TODO: handle this error + sps, _ := NewSPS(nalUnit.rbsp, false) + h.VideoStreams = append( + h.VideoStreams, + &VideoStream{SPS: sps}, + ) + case naluTypePPS: + videoStream := h.VideoStreams[len(h.VideoStreams)-1] + // TODO: handle this error + videoStream.PPS, _ = NewPPS(videoStream.SPS, nalUnit.RBSP(), false) + case naluTypeSliceIDRPicture: + fallthrough + case naluTypeSliceNonIDRPicture: + videoStream := h.VideoStreams[len(h.VideoStreams)-1] + logger.Printf("info: frame number %d\n", len(videoStream.Slices)) + // TODO: handle this error + sliceContext, _ := NewSliceContext(videoStream, nalUnit, nalUnit.RBSP(), true) + videoStream.Slices = append(videoStream.Slices, sliceContext) + } + } +} + +func (r *H264Reader) readNalUnit() (*NalUnit, *bits.BitReader, error) { + // Read to start of NAL + logger.Printf("debug: Seeking NAL %d start\n", len(r.NalUnits)) + + // TODO: Fix this. + for !isStartSequence(nil) { + if err := r.BufferToReader(1); err != nil { + // TODO: should this return an error here. + return nil, nil, nil + } + } + /* + if !r.IsStarted { + logger.Printf("debug: skipping initial NAL zero byte spaces\n") + r.LogStreamPosition() + // Annex B.2 Step 1 + if err := r.Discard(1); err != nil { + logger.Printf("error: while discarding empty byte (Annex B.2:1): %v\n", err) + return nil + } + if err := r.Discard(2); err != nil { + logger.Printf("error: while discarding start code prefix one 3bytes (Annex B.2:2): %v\n", err) + return nil + } + } + */ + startOffset := r.BytesRead() + logger.Printf("debug: Seeking next NAL start\n") + // Read to start of next NAL + so := r.BytesRead() + for so == startOffset || !isStartSequence(nil) { + so = r.BytesRead() + if err := r.BufferToReader(1); err != nil { + // TODO: should this return an error here? + return nil, nil, nil + } + } + // logger.Printf("debug: PreRewind %#v\n", r.Bytes()) + // Rewind back the length of the start sequence + // r.RewindBytes(4) + // logger.Printf("debug: PostRewind %#v\n", r.Bytes()) + endOffset := r.BytesRead() + logger.Printf("debug: found NAL unit with %d bytes from %d to %d\n", endOffset-startOffset, startOffset, endOffset) + nalUnitReader := bits.NewBitReader(nil) + r.NalUnits = append(r.NalUnits, nalUnitReader) + // TODO: this should really take an io.Reader rather than []byte. Need to fix nil + // once this is fixed. + nalUnit, err := NewNalUnit(nil, 0) + if err != nil { + return nil, nil, errors.Wrap(err, "cannot create new nal unit") + } + return nalUnit, nalUnitReader, nil +} + +func isStartSequence(packet []byte) bool { + if len(packet) < len(InitialNALU) { + return false + } + naluSegment := packet[len(packet)-4:] + for i := range InitialNALU { + if naluSegment[i] != InitialNALU[i] { + return false + } + } + return true +} + +func isStartCodeOnePrefix(buf []byte) bool { + for i, b := range buf { + if i < 2 && b != byte(0) { + return false + } + // byte 3 may be 0 or 1 + if i == 3 && b != byte(0) || b != byte(1) { + return false + } + } + logger.Printf("debug: found start code one prefix byte\n") + return true +} + +func isEmpty3Byte(buf []byte) bool { + if len(buf) < 3 { + return false + } + for _, i := range buf[len(buf)-3:] { + if i != 0 { + return false + } + } + return true +} + +// TODO: complete this. +func moreRBSPData(br *bits.BitReader) bool { + // Read until the least significant bit of any remaining bytes + // If the least significant bit is 1, that marks the first bit + // of the rbspTrailingBits() struct. If the bits read is more + // than 0, then there is more RBSP data + var bits uint64 + cnt := 0 + for bits != 1 { + if _, err := br.ReadBits(8); err != nil { + logger.Printf("moreRBSPData error: %v\n", err) + return false + } + cnt++ + } + logger.Printf("moreRBSPData: read %d additional bits\n", cnt) + return cnt > 0 +} + +type field struct { + loc *int + name string + n int +} + +func readFields(br *bits.BitReader, fields []field) error { + for _, f := range fields { + b, err := br.ReadBits(f.n) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not read %s", f.name)) + } + *f.loc = int(b) + } + return nil +} + +type flag struct { + loc *bool + name string +} + +func readFlags(br *bits.BitReader, flags []flag) error { + for _, f := range flags { + b, err := br.ReadBits(1) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not read %s", f.name)) + } + *f.loc = b == 1 + } + return nil +} diff --git a/codec/h264/decode/server.go b/codec/h264/decode/server.go new file mode 100644 index 00000000..f702c1eb --- /dev/null +++ b/codec/h264/decode/server.go @@ -0,0 +1,70 @@ +package h264 + +import ( + // "github.com/nareix/joy4/av" + // "github.com/nareix/joy4/codec/h264parser" + // "github.com/nareix/joy4/format/ts" + "io" + "log" + "net" + "os" + "os/signal" + + "github.com/ausocean/h264decode/h264/bits" +) + +// InitialNALU indicates the start of a h264 packet +// 0 - Forbidden 0 bit; always 0 +// 1,2 - NRI +// 3,4,5,6,7 - Type +var ( + InitialNALU = []byte{0, 0, 0, 1} + Initial3BNALU = []byte{0, 0, 1} + logger *log.Logger + streamOffset = 0 +) + +func init() { + logger = log.New(os.Stderr, "streamer ", log.Lshortfile|log.Lmicroseconds) +} + +func ByteStreamReader(connection net.Conn) { + logger.Printf("opened bytestream\n") + defer connection.Close() + handleConnection(connection) +} + +func handleConnection(connection io.Reader) { + logger.Printf("debug: handling connection\n") + streamFilename := "/home/bruce/devel/go/src/github.com/mrmod/cvnightlife/output.mp4" + _ = os.Remove(streamFilename) + debugFile, err := os.Create(streamFilename) + if err != nil { + panic(err) + } + streamReader := &H264Reader{ + Stream: connection, + // TODO: need to give this an io.Reader, not nil. + BitReader: bits.NewBitReader(nil), + DebugFile: debugFile, + } + c := make(chan os.Signal, 1) + signal.Notify(c) + go func() { + logger.Printf("debug: waiting on signals\n") + s := <-c + logger.Printf("info: %v received, closing stream file\n", s) + streamReader.DebugFile.Close() + os.Exit(0) + }() + + defer func() { + if r := recover(); r != nil { + logger.Printf("fatal: recovered: %v\n", r) + logger.Printf("info: closing streamfile\n") + streamReader.DebugFile.Close() + os.Exit(1) + } + }() + streamReader.Start() +} diff --git a/codec/h264/decode/slice.go b/codec/h264/decode/slice.go new file mode 100644 index 00000000..251989cc --- /dev/null +++ b/codec/h264/decode/slice.go @@ -0,0 +1,1369 @@ +package h264 + +import ( + "bytes" + "fmt" + "math" + + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +// Chroma formats as defined in section 6.2, tab 6-1. +const ( + chromaMonochrome = iota + chroma420 + chroma422 + chroma444 +) + +type VideoStream struct { + SPS *SPS + PPS *PPS + Slices []*SliceContext +} +type SliceContext struct { + *NalUnit + *SPS + *PPS + *Slice +} +type Slice struct { + Header *SliceHeader + Data *SliceData +} +type SliceHeader struct { + FirstMbInSlice int + SliceType int + PPSID int + ColorPlaneID int + FrameNum int + FieldPic bool + BottomField bool + IDRPicID int + PicOrderCntLsb int + DeltaPicOrderCntBottom int + DeltaPicOrderCnt []int + RedundantPicCnt int + DirectSpatialMvPred bool + NumRefIdxActiveOverride bool + NumRefIdxL0ActiveMinus1 int + NumRefIdxL1ActiveMinus1 int + CabacInit int + SliceQpDelta int + SpForSwitch bool + SliceQsDelta int + DisableDeblockingFilter int + SliceAlphaC0OffsetDiv2 int + SliceBetaOffsetDiv2 int + SliceGroupChangeCycle int + RefPicListModificationFlagL0 bool + ModificationOfPicNums int + AbsDiffPicNumMinus1 int + LongTermPicNum int + RefPicListModificationFlagL1 bool + LumaLog2WeightDenom int + ChromaLog2WeightDenom int + ChromaArrayType int + LumaWeightL0Flag bool + LumaWeightL0 []int + LumaOffsetL0 []int + ChromaWeightL0Flag bool + ChromaWeightL0 [][]int + ChromaOffsetL0 [][]int + LumaWeightL1Flag bool + LumaWeightL1 []int + LumaOffsetL1 []int + ChromaWeightL1Flag bool + ChromaWeightL1 [][]int + ChromaOffsetL1 [][]int + NoOutputOfPriorPicsFlag bool + LongTermReferenceFlag bool + AdaptiveRefPicMarkingModeFlag bool + MemoryManagementControlOperation int + DifferenceOfPicNumsMinus1 int + LongTermFrameIdx int + MaxLongTermFrameIdxPlus1 int +} + +type SliceData struct { + BitReader *bits.BitReader + CabacAlignmentOneBit int + MbSkipRun int + MbSkipFlag bool + MbFieldDecodingFlag bool + EndOfSliceFlag bool + MbType int + MbTypeName string + SliceTypeName string + PcmAlignmentZeroBit int + PcmSampleLuma []int + PcmSampleChroma []int + TransformSize8x8Flag bool + CodedBlockPattern int + MbQpDelta int + PrevIntra4x4PredModeFlag []int + RemIntra4x4PredMode []int + PrevIntra8x8PredModeFlag []int + RemIntra8x8PredMode []int + IntraChromaPredMode int + RefIdxL0 []int + RefIdxL1 []int + MvdL0 [][][]int + MvdL1 [][][]int +} + +// Table 7-6 +var sliceTypeMap = map[int]string{ + 0: "P", + 1: "B", + 2: "I", + 3: "SP", + 4: "SI", + 5: "P", + 6: "B", + 7: "I", + 8: "SP", + 9: "SI", +} + +func flagVal(b bool) int { + if b { + return 1 + } + return 0 +} + +// context-adaptive arithmetic entropy-coded element (CABAC) +// 9.3 +// When parsing the slice date of a slice (7.3.4) the initialization is 9.3.1 +func (d SliceData) ae(v int) int { + // 9.3.1.1 : CABAC context initialization ctxIdx + return 0 +} + +// 8.2.2 +func MbToSliceGroupMap(sps *SPS, pps *PPS, header *SliceHeader) []int { + mbaffFrameFlag := 0 + if sps.MBAdaptiveFrameField && !header.FieldPic { + mbaffFrameFlag = 1 + } + mapUnitToSliceGroupMap := MapUnitToSliceGroupMap(sps, pps, header) + mbToSliceGroupMap := []int{} + for i := 0; i <= PicSizeInMbs(sps, header)-1; i++ { + if sps.FrameMbsOnly || header.FieldPic { + mbToSliceGroupMap = append(mbToSliceGroupMap, mapUnitToSliceGroupMap[i]) + continue + } + if mbaffFrameFlag == 1 { + mbToSliceGroupMap = append(mbToSliceGroupMap, mapUnitToSliceGroupMap[i/2]) + continue + } + if !sps.FrameMbsOnly && !sps.MBAdaptiveFrameField && !header.FieldPic { + mbToSliceGroupMap = append( + mbToSliceGroupMap, + mapUnitToSliceGroupMap[(i/(2*PicWidthInMbs(sps)))*PicWidthInMbs(sps)+(i%PicWidthInMbs(sps))]) + } + } + return mbToSliceGroupMap + +} +func PicWidthInMbs(sps *SPS) int { + return sps.PicWidthInMbsMinus1 + 1 +} +func PicHeightInMapUnits(sps *SPS) int { + return sps.PicHeightInMapUnitsMinus1 + 1 +} +func PicSizeInMapUnits(sps *SPS) int { + return PicWidthInMbs(sps) * PicHeightInMapUnits(sps) +} +func FrameHeightInMbs(sps *SPS) int { + return (2 - flagVal(sps.FrameMbsOnly)) * PicHeightInMapUnits(sps) +} +func PicHeightInMbs(sps *SPS, header *SliceHeader) int { + return FrameHeightInMbs(sps) / (1 + flagVal(header.FieldPic)) +} +func PicSizeInMbs(sps *SPS, header *SliceHeader) int { + return PicWidthInMbs(sps) * PicHeightInMbs(sps, header) +} + +// table 6-1 +func SubWidthC(sps *SPS) int { + n := 17 + if sps.UseSeparateColorPlane { + if sps.ChromaFormat == chroma444 { + return n + } + } + + switch sps.ChromaFormat { + case chromaMonochrome: + return n + case chroma420: + n = 2 + case chroma422: + n = 2 + case chroma444: + n = 1 + + } + return n +} +func SubHeightC(sps *SPS) int { + n := 17 + if sps.UseSeparateColorPlane { + if sps.ChromaFormat == chroma444 { + return n + } + } + switch sps.ChromaFormat { + case chromaMonochrome: + return n + case chroma420: + n = 2 + case chroma422: + n = 1 + case chroma444: + n = 1 + + } + return n +} + +// 7-36 +func CodedBlockPatternLuma(data *SliceData) int { + return data.CodedBlockPattern % 16 +} +func CodedBlockPatternChroma(data *SliceData) int { + return data.CodedBlockPattern / 16 +} + +// dependencyId see Annex G.8.8.1 +// Also G7.3.1.1 nal_unit_header_svc_extension +func DQId(nalUnit *NalUnit) int { + return (nalUnit.DependencyId << 4) + nalUnit.QualityId +} + +// Annex G p527 +func NumMbPart(nalUnit *NalUnit, sps *SPS, header *SliceHeader, data *SliceData) int { + sliceType := sliceTypeMap[header.SliceType] + numMbPart := 0 + if MbTypeName(sliceType, CurrMbAddr(sps, header)) == "B_SKIP" || MbTypeName(sliceType, CurrMbAddr(sps, header)) == "B_Direct_16x16" { + if DQId(nalUnit) == 0 && nalUnit.Type != 20 { + numMbPart = 4 + } else if DQId(nalUnit) > 0 && nalUnit.Type == 20 { + numMbPart = 1 + } + } else if MbTypeName(sliceType, CurrMbAddr(sps, header)) != "B_SKIP" && MbTypeName(sliceType, CurrMbAddr(sps, header)) != "B_Direct_16x16" { + numMbPart = CurrMbAddr(sps, header) + + } + return numMbPart +} + +func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { + var cabac *CABAC + sliceType := sliceTypeMap[sliceContext.Slice.Header.SliceType] + mbPartPredMode, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, 0) + if err != nil { + return errors.Wrap(err, "could not get mbPartPredMode") + } + if mbPartPredMode == intra4x4 || mbPartPredMode == intra8x8 || mbPartPredMode == intra16x16 { + if mbPartPredMode == intra4x4 { + for luma4x4BlkIdx := 0; luma4x4BlkIdx < 16; luma4x4BlkIdx++ { + var v int + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: 1 bit or ae(v) + binarization := NewBinarization( + "PrevIntra4x4PredModeFlag", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + cabac = initCabac(binarization, sliceContext) + _ = cabac + logger.Printf("TODO: ae for PevIntra4x4PredModeFlag[%d]\n", luma4x4BlkIdx) + } else { + b, err := br.ReadBits(1) + if err != nil { + return errors.Wrap(err, "could not read PrevIntra4x4PredModeFlag") + } + v = int(b) + } + sliceContext.Slice.Data.PrevIntra4x4PredModeFlag = append( + sliceContext.Slice.Data.PrevIntra4x4PredModeFlag, + v) + if sliceContext.Slice.Data.PrevIntra4x4PredModeFlag[luma4x4BlkIdx] == 0 { + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: 3 bits or ae(v) + binarization := NewBinarization( + "RemIntra4x4PredMode", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for RemIntra4x4PredMode[%d]\n", luma4x4BlkIdx) + } else { + b, err := br.ReadBits(3) + if err != nil { + return errors.Wrap(err, "could not read RemIntra4x4PredMode") + } + v = int(b) + } + if len(sliceContext.Slice.Data.RemIntra4x4PredMode) < luma4x4BlkIdx { + sliceContext.Slice.Data.RemIntra4x4PredMode = append( + sliceContext.Slice.Data.RemIntra4x4PredMode, + make([]int, luma4x4BlkIdx-len(sliceContext.Slice.Data.RemIntra4x4PredMode)+1)...) + } + sliceContext.Slice.Data.RemIntra4x4PredMode[luma4x4BlkIdx] = v + } + } + } + if mbPartPredMode == intra8x8 { + for luma8x8BlkIdx := 0; luma8x8BlkIdx < 4; luma8x8BlkIdx++ { + sliceContext.Update(sliceContext.Slice.Header, sliceContext.Slice.Data) + var v int + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: 1 bit or ae(v) + binarization := NewBinarization("PrevIntra8x8PredModeFlag", sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for PrevIntra8x8PredModeFlag[%d]\n", luma8x8BlkIdx) + } else { + b, err := br.ReadBits(1) + if err != nil { + return errors.Wrap(err, "could not read PrevIntra8x8PredModeFlag") + } + v = int(b) + } + sliceContext.Slice.Data.PrevIntra8x8PredModeFlag = append( + sliceContext.Slice.Data.PrevIntra8x8PredModeFlag, v) + if sliceContext.Slice.Data.PrevIntra8x8PredModeFlag[luma8x8BlkIdx] == 0 { + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: 3 bits or ae(v) + binarization := NewBinarization( + "RemIntra8x8PredMode", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for RemIntra8x8PredMode[%d]\n", luma8x8BlkIdx) + } else { + b, err := br.ReadBits(3) + if err != nil { + return errors.Wrap(err, "could not read RemIntra8x8PredMode") + } + v = int(b) + } + if len(sliceContext.Slice.Data.RemIntra8x8PredMode) < luma8x8BlkIdx { + sliceContext.Slice.Data.RemIntra8x8PredMode = append( + sliceContext.Slice.Data.RemIntra8x8PredMode, + make([]int, luma8x8BlkIdx-len(sliceContext.Slice.Data.RemIntra8x8PredMode)+1)...) + } + sliceContext.Slice.Data.RemIntra8x8PredMode[luma8x8BlkIdx] = v + } + } + + } + if sliceContext.Slice.Header.ChromaArrayType == 1 || sliceContext.Slice.Header.ChromaArrayType == 2 { + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: ue(v) or ae(v) + binarization := NewBinarization( + "IntraChromaPredMode", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for IntraChromaPredMode\n") + } else { + var err error + sliceContext.Slice.Data.IntraChromaPredMode, err = readUe(nil) + if err != nil { + return errors.Wrap(err, "could not parse IntraChromaPredMode") + } + } + } + + } else if mbPartPredMode != direct { + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + sliceContext.Update(sliceContext.Slice.Header, sliceContext.Slice.Data) + m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get mbPartPredMode for loop 1 mbPartIdx: %d", mbPartIdx)) + } + if (sliceContext.Slice.Header.NumRefIdxL0ActiveMinus1 > 0 || sliceContext.Slice.Data.MbFieldDecodingFlag != sliceContext.Slice.Header.FieldPic) && m != predL1 { + logger.Printf("\tTODO: refIdxL0[%d] te or ae(v)\n", mbPartIdx) + if len(sliceContext.Slice.Data.RefIdxL0) < mbPartIdx { + sliceContext.Slice.Data.RefIdxL0 = append( + sliceContext.Slice.Data.RefIdxL0, make([]int, mbPartIdx-len(sliceContext.Slice.Data.RefIdxL0)+1)...) + } + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: te(v) or ae(v) + binarization := NewBinarization( + "RefIdxL0", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for RefIdxL0[%d]\n", mbPartIdx) + } else { + // TODO: Only one reference picture is used for inter-prediction, + // then the value should be 0 + if MbaffFrameFlag(sliceContext.SPS, sliceContext.Slice.Header) == 0 || !sliceContext.Slice.Data.MbFieldDecodingFlag { + sliceContext.Slice.Data.RefIdxL0[mbPartIdx], _ = readTe( + nil, + uint(sliceContext.Slice.Header.NumRefIdxL0ActiveMinus1)) + } else { + rangeMax := 2*sliceContext.Slice.Header.NumRefIdxL0ActiveMinus1 + 1 + sliceContext.Slice.Data.RefIdxL0[mbPartIdx], _ = readTe( + nil, + uint(rangeMax)) + } + } + } + } + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get mbPartPredMode for loop 2 mbPartIdx: %d", mbPartIdx)) + } + if m != predL1 { + for compIdx := 0; compIdx < 2; compIdx++ { + if len(sliceContext.Slice.Data.MvdL0) < mbPartIdx { + sliceContext.Slice.Data.MvdL0 = append( + sliceContext.Slice.Data.MvdL0, + make([][][]int, mbPartIdx-len(sliceContext.Slice.Data.MvdL0)+1)...) + } + if len(sliceContext.Slice.Data.MvdL0[mbPartIdx][0]) < compIdx { + sliceContext.Slice.Data.MvdL0[mbPartIdx][0] = append( + sliceContext.Slice.Data.MvdL0[mbPartIdx][0], + make([]int, compIdx-len(sliceContext.Slice.Data.MvdL0[mbPartIdx][0])+1)...) + } + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: se(v) or ae(v) + if compIdx == 0 { + binarization := NewBinarization( + "MvdLnEnd0", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + } else if compIdx == 1 { + binarization := NewBinarization( + "MvdLnEnd1", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + } + logger.Printf("TODO: ae for MvdL0[%d][0][%d]\n", mbPartIdx, compIdx) + } else { + sliceContext.Slice.Data.MvdL0[mbPartIdx][0][compIdx], _ = readSe(nil) + } + } + } + } + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + sliceContext.Update(sliceContext.Slice.Header, sliceContext.Slice.Data) + m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get mbPartPredMode for loop 3 mbPartIdx: %d", mbPartIdx)) + } + if m != predL0 { + for compIdx := 0; compIdx < 2; compIdx++ { + if len(sliceContext.Slice.Data.MvdL1) < mbPartIdx { + sliceContext.Slice.Data.MvdL1 = append( + sliceContext.Slice.Data.MvdL1, + make([][][]int, mbPartIdx-len(sliceContext.Slice.Data.MvdL1)+1)...) + } + if len(sliceContext.Slice.Data.MvdL1[mbPartIdx][0]) < compIdx { + sliceContext.Slice.Data.MvdL1[mbPartIdx][0] = append( + sliceContext.Slice.Data.MvdL0[mbPartIdx][0], + make([]int, compIdx-len(sliceContext.Slice.Data.MvdL1[mbPartIdx][0])+1)...) + } + if sliceContext.PPS.EntropyCodingMode == 1 { + if compIdx == 0 { + binarization := NewBinarization( + "MvdLnEnd0", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + } else if compIdx == 1 { + binarization := NewBinarization( + "MvdLnEnd1", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + } + // TODO: se(v) or ae(v) + logger.Printf("TODO: ae for MvdL1[%d][0][%d]\n", mbPartIdx, compIdx) + } else { + sliceContext.Slice.Data.MvdL1[mbPartIdx][0][compIdx], _ = readSe(nil) + } + } + } + } + } + return nil +} + +// 8.2.2.1 +func MapUnitToSliceGroupMap(sps *SPS, pps *PPS, header *SliceHeader) []int { + mapUnitToSliceGroupMap := []int{} + picSizeInMapUnits := PicSizeInMapUnits(sps) + if pps.NumSliceGroupsMinus1 == 0 { + // 0 to PicSizeInMapUnits -1 inclusive + for i := 0; i <= picSizeInMapUnits-1; i++ { + mapUnitToSliceGroupMap = append(mapUnitToSliceGroupMap, 0) + } + } else { + switch pps.SliceGroupMapType { + case 0: + // 8.2.2.1 + i := 0 + for i < picSizeInMapUnits { + // iGroup should be incremented in the pps.RunLengthMinus1 index operation. There may be a bug here + for iGroup := 0; iGroup <= pps.NumSliceGroupsMinus1 && i < picSizeInMapUnits; i += pps.RunLengthMinus1[iGroup+1] + 1 { + for j := 0; j < pps.RunLengthMinus1[iGroup] && i+j < picSizeInMapUnits; j++ { + if len(mapUnitToSliceGroupMap) < i+j { + mapUnitToSliceGroupMap = append( + mapUnitToSliceGroupMap, + make([]int, (i+j)-len(mapUnitToSliceGroupMap)+1)...) + } + mapUnitToSliceGroupMap[i+j] = iGroup + } + } + } + case 1: + // 8.2.2.2 + for i := 0; i < picSizeInMapUnits; i++ { + v := ((i % PicWidthInMbs(sps)) + (((i / PicWidthInMbs(sps)) * (pps.NumSliceGroupsMinus1 + 1)) / 2)) % (pps.NumSliceGroupsMinus1 + 1) + mapUnitToSliceGroupMap = append(mapUnitToSliceGroupMap, v) + } + case 2: + // 8.2.2.3 + for i := 0; i < picSizeInMapUnits; i++ { + mapUnitToSliceGroupMap = append(mapUnitToSliceGroupMap, pps.NumSliceGroupsMinus1) + } + for iGroup := pps.NumSliceGroupsMinus1 - 1; iGroup >= 0; iGroup-- { + yTopLeft := pps.TopLeft[iGroup] / PicWidthInMbs(sps) + xTopLeft := pps.TopLeft[iGroup] % PicWidthInMbs(sps) + yBottomRight := pps.BottomRight[iGroup] / PicWidthInMbs(sps) + xBottomRight := pps.BottomRight[iGroup] % PicWidthInMbs(sps) + for y := yTopLeft; y <= yBottomRight; y++ { + for x := xTopLeft; x <= xBottomRight; x++ { + idx := y*PicWidthInMbs(sps) + x + if len(mapUnitToSliceGroupMap) < idx { + mapUnitToSliceGroupMap = append( + mapUnitToSliceGroupMap, + make([]int, idx-len(mapUnitToSliceGroupMap)+1)...) + mapUnitToSliceGroupMap[idx] = iGroup + } + } + } + } + + case 3: + // 8.2.2.4 + // TODO + case 4: + // 8.2.2.5 + // TODO + case 5: + // 8.2.2.6 + // TODO + case 6: + // 8.2.2.7 + // TODO + } + } + // 8.2.2.8 + // Convert mapUnitToSliceGroupMap to MbToSliceGroupMap + return mapUnitToSliceGroupMap +} +func nextMbAddress(n int, sps *SPS, pps *PPS, header *SliceHeader) int { + i := n + 1 + // picSizeInMbs is the number of macroblocks in picture 0 + // 7-13 + // PicWidthInMbs = sps.PicWidthInMbsMinus1 + 1 + // PicHeightInMapUnits = sps.PicHeightInMapUnitsMinus1 + 1 + // 7-29 + // picSizeInMbs = PicWidthInMbs * PicHeightInMbs + // 7-26 + // PicHeightInMbs = FrameHeightInMbs / (1 + header.fieldPicFlag) + // 7-18 + // FrameHeightInMbs = (2 - ps.FrameMbsOnly) * PicHeightInMapUnits + picWidthInMbs := sps.PicWidthInMbsMinus1 + 1 + picHeightInMapUnits := sps.PicHeightInMapUnitsMinus1 + 1 + frameHeightInMbs := (2 - flagVal(sps.FrameMbsOnly)) * picHeightInMapUnits + picHeightInMbs := frameHeightInMbs / (1 + flagVal(header.FieldPic)) + picSizeInMbs := picWidthInMbs * picHeightInMbs + mbToSliceGroupMap := MbToSliceGroupMap(sps, pps, header) + for i < picSizeInMbs && mbToSliceGroupMap[i] != mbToSliceGroupMap[i] { + i++ + } + return i +} + +func CurrMbAddr(sps *SPS, header *SliceHeader) int { + mbaffFrameFlag := 0 + if sps.MBAdaptiveFrameField && !header.FieldPic { + mbaffFrameFlag = 1 + } + + return header.FirstMbInSlice * (1 * mbaffFrameFlag) +} + +func MbaffFrameFlag(sps *SPS, header *SliceHeader) int { + if sps.MBAdaptiveFrameField && !header.FieldPic { + return 1 + } + return 0 +} + +func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, error) { + var cabac *CABAC + var err error + sliceContext.Slice.Data = &SliceData{BitReader: br} + // TODO: Why is this being initialized here? + // initCabac(sliceContext) + if sliceContext.PPS.EntropyCodingMode == 1 { + for !br.ByteAligned() { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read CabacAlignmentOneBit") + } + sliceContext.Slice.Data.CabacAlignmentOneBit = int(b) + } + } + mbaffFrameFlag := 0 + if sliceContext.SPS.MBAdaptiveFrameField && !sliceContext.Slice.Header.FieldPic { + mbaffFrameFlag = 1 + } + currMbAddr := sliceContext.Slice.Header.FirstMbInSlice * (1 * mbaffFrameFlag) + + moreDataFlag := true + prevMbSkipped := 0 + sliceContext.Slice.Data.SliceTypeName = sliceTypeMap[sliceContext.Slice.Header.SliceType] + sliceContext.Slice.Data.MbTypeName = MbTypeName(sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType) + logger.Printf("debug: \tSliceData: Processing moreData: %v\n", moreDataFlag) + for moreDataFlag { + logger.Printf("debug: \tLooking for more sliceContext.Slice.Data in slice type %s\n", sliceContext.Slice.Data.SliceTypeName) + if sliceContext.Slice.Data.SliceTypeName != "I" && sliceContext.Slice.Data.SliceTypeName != "SI" { + logger.Printf("debug: \tNonI/SI slice, processing moreData\n") + if sliceContext.PPS.EntropyCodingMode == 0 { + sliceContext.Slice.Data.MbSkipRun, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MbSkipRun") + } + + if sliceContext.Slice.Data.MbSkipRun > 0 { + prevMbSkipped = 1 + } + for i := 0; i < sliceContext.Slice.Data.MbSkipRun; i++ { + // nextMbAddress(currMbAdd + currMbAddr = nextMbAddress(currMbAddr, sliceContext.SPS, sliceContext.PPS, sliceContext.Slice.Header) + } + if sliceContext.Slice.Data.MbSkipRun > 0 { + moreDataFlag = moreRBSPData(br) + } + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read MbSkipFlag") + } + sliceContext.Slice.Data.MbSkipFlag = b == 1 + + moreDataFlag = !sliceContext.Slice.Data.MbSkipFlag + } + } + if moreDataFlag { + if mbaffFrameFlag == 1 && (currMbAddr%2 == 0 || (currMbAddr%2 == 1 && prevMbSkipped == 1)) { + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: ae implementation + binarization := NewBinarization("MbFieldDecodingFlag", sliceContext.Slice.Data) + // TODO: this should take a BitReader where the nil is. + binarization.Decode(sliceContext, br, nil) + + logger.Printf("TODO: ae for MbFieldDecodingFlag\n") + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read MbFieldDecodingFlag") + } + sliceContext.Slice.Data.MbFieldDecodingFlag = b == 1 + } + } + + // BEGIN: macroblockLayer() + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: ae implementation + binarization := NewBinarization("MbType", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + _ = cabac + // TODO: remove bytes parameter from this function. + binarization.Decode(sliceContext, br, nil) + if binarization.PrefixSuffix { + logger.Printf("debug: MBType binarization has prefix and suffix\n") + } + bits := []int{} + for binIdx := 0; binarization.IsBinStringMatch(bits); binIdx++ { + newBit, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read bit") + } + if binarization.UseDecodeBypass == 1 { + // DecodeBypass + logger.Printf("TODO: decodeBypass is set: 9.3.3.2.3") + codIRange, codIOffset, err := initDecodingEngine(sliceContext.Slice.Data.BitReader) + if err != nil { + return nil, errors.Wrap(err, "could not initialise decoding engine") + } + // Initialize the decoder + // TODO: When should the suffix of MaxBinIdxCtx be used and when just the prefix? + // TODO: When should the suffix of CtxIdxOffset be used? + arithmeticDecoder, err := NewArithmeticDecoding( + sliceContext, + binarization, + CtxIdx( + binarization.binIdx, + binarization.MaxBinIdxCtx.Prefix, + binarization.CtxIdxOffset.Prefix, + ), + codIRange, + codIOffset, + ) + if err != nil { + return nil, errors.Wrap(err, "error from NewArithmeticDecoding") + } + // Bypass decoding + codIOffset, _, err = arithmeticDecoder.DecodeBypass( + sliceContext.Slice.Data, + codIRange, + codIOffset, + ) + if err != nil { + return nil, errors.Wrap(err, "could not DecodeBypass") + } + // End DecodeBypass + + } else { + // DO 9.3.3.1 + ctxIdx := CtxIdx( + binIdx, + binarization.MaxBinIdxCtx.Prefix, + binarization.CtxIdxOffset.Prefix) + if binarization.MaxBinIdxCtx.IsPrefixSuffix { + logger.Printf("TODO: Handle PrefixSuffix binarization\n") + } + logger.Printf("debug: MBType ctxIdx for %d is %d\n", binIdx, ctxIdx) + // Then 9.3.3.2 + codIRange, codIOffset, err := initDecodingEngine(br) + if err != nil { + return nil, errors.Wrap(err, "error from initDecodingEngine") + } + logger.Printf("debug: coding engine initialized: %d/%d\n", codIRange, codIOffset) + } + bits = append(bits, int(newBit)) + } + + logger.Printf("TODO: ae for MBType\n") + } else { + sliceContext.Slice.Data.MbType, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MbType") + } + } + if sliceContext.Slice.Data.MbTypeName == "I_PCM" { + for !br.ByteAligned() { + _, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read PCMAlignmentZeroBit") + } + } + // 7-3 p95 + bitDepthY := 8 + sliceContext.SPS.BitDepthLumaMinus8 + for i := 0; i < 256; i++ { + s, err := br.ReadBits(bitDepthY) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("could not read PcmSampleLuma[%d]", i)) + } + sliceContext.Slice.Data.PcmSampleLuma = append( + sliceContext.Slice.Data.PcmSampleLuma, + int(s)) + } + // 9.3.1 p 246 + // cabac = initCabac(binarization, sliceContext) + // 6-1 p 47 + mbWidthC := 16 / SubWidthC(sliceContext.SPS) + mbHeightC := 16 / SubHeightC(sliceContext.SPS) + // if monochrome + if sliceContext.SPS.ChromaFormat == chromaMonochrome || sliceContext.SPS.UseSeparateColorPlane { + mbWidthC = 0 + mbHeightC = 0 + } + + bitDepthC := 8 + sliceContext.SPS.BitDepthChromaMinus8 + for i := 0; i < 2*mbWidthC*mbHeightC; i++ { + s, err := br.ReadBits(bitDepthC) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("could not read PcmSampleChroma[%d]", i)) + } + sliceContext.Slice.Data.PcmSampleChroma = append( + sliceContext.Slice.Data.PcmSampleChroma, + int(s)) + } + // 9.3.1 p 246 + // cabac = initCabac(binarization, sliceContext) + + } else { + noSubMbPartSizeLessThan8x8Flag := 1 + m, err := MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0) + if err != nil { + return nil, errors.Wrap(err, "could not get mbPartPredMode") + } + if sliceContext.Slice.Data.MbTypeName == "I_NxN" && m != intra16x16 && NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data) == 4 { + logger.Printf("\tTODO: subMbPred\n") + /* + subMbType := SubMbPred(sliceContext.Slice.Data.MbType) + for mbPartIdx := 0; mbPartIdx < 4; mbPartIdx++ { + if subMbType[mbPartIdx] != "B_Direct_8x8" { + if NumbSubMbPart(subMbType[mbPartIdx]) > 1 { + noSubMbPartSizeLessThan8x8Flag = 0 + } + } else if !sliceContext.SPS.Direct8x8Inference { + noSubMbPartSizeLessThan8x8Flag = 0 + } + } + */ + } else { + if sliceContext.PPS.Transform8x8Mode == 1 && sliceContext.Slice.Data.MbTypeName == "I_NxN" { + // TODO + // 1 bit or ae(v) + // If sliceContext.PPS.EntropyCodingMode == 1, use ae(v) + if sliceContext.PPS.EntropyCodingMode == 1 { + binarization := NewBinarization("TransformSize8x8Flag", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + binarization.Decode(sliceContext, br, nil) + + logger.Println("TODO: ae(v) for TransformSize8x8Flag") + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read TransformSize8x8Flag") + } + sliceContext.Slice.Data.TransformSize8x8Flag = b == 1 + } + } + // TODO: fix nil argument for. + MbPred(sliceContext, br, nil) + } + m, err = MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0) + if err != nil { + return nil, errors.Wrap(err, "could not get mbPartPredMode") + } + if m != intra16x16 { + // TODO: me, ae + logger.Printf("TODO: CodedBlockPattern pending me/ae implementation\n") + if sliceContext.PPS.EntropyCodingMode == 1 { + binarization := NewBinarization("CodedBlockPattern", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + // TODO: fix nil argument. + binarization.Decode(sliceContext, br, nil) + + logger.Printf("TODO: ae for CodedBlockPattern\n") + } else { + me, _ := readMe( + nil, + uint(sliceContext.Slice.Header.ChromaArrayType), + // TODO: fix this + //MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0))) + 0) + sliceContext.Slice.Data.CodedBlockPattern = int(me) + } + + // sliceContext.Slice.Data.CodedBlockPattern = me(v) | ae(v) + if CodedBlockPatternLuma(sliceContext.Slice.Data) > 0 && sliceContext.PPS.Transform8x8Mode == 1 && sliceContext.Slice.Data.MbTypeName != "I_NxN" && noSubMbPartSizeLessThan8x8Flag == 1 && (sliceContext.Slice.Data.MbTypeName != "B_Direct_16x16" || sliceContext.SPS.Direct8x8Inference) { + // TODO: 1 bit or ae(v) + if sliceContext.PPS.EntropyCodingMode == 1 { + binarization := NewBinarization("Transform8x8Flag", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + // TODO: fix nil argument. + binarization.Decode(sliceContext, br, nil) + + logger.Printf("TODO: ae for TranformSize8x8Flag\n") + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "coult not read TransformSize8x8Flag") + } + sliceContext.Slice.Data.TransformSize8x8Flag = b == 1 + } + } + } + m, err = MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0) + if err != nil { + return nil, errors.Wrap(err, "could not get mbPartPredMode") + } + if CodedBlockPatternLuma(sliceContext.Slice.Data) > 0 || CodedBlockPatternChroma(sliceContext.Slice.Data) > 0 || m == intra16x16 { + // TODO: se or ae(v) + if sliceContext.PPS.EntropyCodingMode == 1 { + binarization := NewBinarization("MbQpDelta", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + // TODO; fix nil argument + binarization.Decode(sliceContext, br, nil) + + logger.Printf("TODO: ae for MbQpDelta\n") + } else { + sliceContext.Slice.Data.MbQpDelta, _ = readSe(nil) + } + + } + } + + } // END MacroblockLayer + if sliceContext.PPS.EntropyCodingMode == 0 { + moreDataFlag = moreRBSPData(br) + } else { + if sliceContext.Slice.Data.SliceTypeName != "I" && sliceContext.Slice.Data.SliceTypeName != "SI" { + if sliceContext.Slice.Data.MbSkipFlag { + prevMbSkipped = 1 + } else { + prevMbSkipped = 0 + } + } + if mbaffFrameFlag == 1 && currMbAddr%2 == 0 { + moreDataFlag = true + } else { + // TODO: ae implementation + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read EndOfSliceFlag") + } + sliceContext.Slice.Data.EndOfSliceFlag = b == 1 + moreDataFlag = !sliceContext.Slice.Data.EndOfSliceFlag + } + } + currMbAddr = nextMbAddress(currMbAddr, sliceContext.SPS, sliceContext.PPS, sliceContext.Slice.Header) + } // END while moreDataFlag + return sliceContext.Slice.Data, nil +} + +func (c *SliceContext) Update(header *SliceHeader, data *SliceData) { + c.Slice = &Slice{Header: header, Data: data} +} +func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, showPacket bool) (*SliceContext, error) { + var err error + sps := videoStream.SPS + pps := videoStream.PPS + logger.Printf("debug: %s RBSP %d bytes %d bits == \n", NALUnitType[nalUnit.Type], len(rbsp), len(rbsp)*8) + logger.Printf("debug: \t%#v\n", rbsp[0:8]) + var idrPic bool + if nalUnit.Type == 5 { + idrPic = true + } + header := SliceHeader{} + if sps.UseSeparateColorPlane { + header.ChromaArrayType = 0 + } else { + header.ChromaArrayType = sps.ChromaFormat + } + br := bits.NewBitReader(bytes.NewReader(rbsp)) + + header.FirstMbInSlice, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FirstMbInSlice") + } + + header.SliceType, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceType") + } + + sliceType := sliceTypeMap[header.SliceType] + logger.Printf("debug: %s (%s) slice of %d bytes\n", NALUnitType[nalUnit.Type], sliceType, len(rbsp)) + header.PPSID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PPSID") + } + + if sps.UseSeparateColorPlane { + b, err := br.ReadBits(2) + if err != nil { + return nil, errors.Wrap(err, "could not read ColorPlaneID") + } + header.ColorPlaneID = int(b) + } + // TODO: See 7.4.3 + // header.FrameNum = b.NextField("FrameNum", 0) + if !sps.FrameMbsOnly { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read FieldPic") + } + header.FieldPic = b == 1 + if header.FieldPic { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read BottomField") + } + header.BottomField = b == 1 + } + } + if idrPic { + header.IDRPicID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse IDRPicID") + } + } + if sps.PicOrderCountType == 0 { + b, err := br.ReadBits(sps.Log2MaxPicOrderCntLSBMin4 + 4) + if err != nil { + return nil, errors.Wrap(err, "could not read PicOrderCntLsb") + } + header.PicOrderCntLsb = int(b) + + if pps.BottomFieldPicOrderInFramePresent && !header.FieldPic { + header.DeltaPicOrderCntBottom, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse DeltaPicOrderCntBottom") + } + } + } + if sps.PicOrderCountType == 1 && !sps.DeltaPicOrderAlwaysZero { + header.DeltaPicOrderCnt[0], err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse DeltaPicOrderCnt") + } + + if pps.BottomFieldPicOrderInFramePresent && !header.FieldPic { + header.DeltaPicOrderCnt[1], err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse DeltaPicOrderCnt") + } + } + } + if pps.RedundantPicCntPresent { + header.RedundantPicCnt, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse RedundantPicCnt") + } + } + if sliceType == "B" { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read DirectSpatialMvPred") + } + header.DirectSpatialMvPred = b == 1 + } + if sliceType == "B" || sliceType == "SP" { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read NumRefIdxActiveOverride") + } + header.NumRefIdxActiveOverride = b == 1 + + if header.NumRefIdxActiveOverride { + header.NumRefIdxL0ActiveMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse NumRefIdxL0ActiveMinus1") + } + if sliceType == "B" { + header.NumRefIdxL1ActiveMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse NumRefIdxL1ActiveMinus1") + } + } + } + } + + if nalUnit.Type == 20 || nalUnit.Type == 21 { + // Annex H + // H.7.3.3.1.1 + // refPicListMvcModifications() + } else { + // 7.3.3.1 + if header.SliceType%5 != 2 && header.SliceType%5 != 4 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read RefPicListModificationFlagL0") + } + header.RefPicListModificationFlagL0 = b == 1 + + if header.RefPicListModificationFlagL0 { + for header.ModificationOfPicNums != 3 { + header.ModificationOfPicNums, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") + } + + if header.ModificationOfPicNums == 0 || header.ModificationOfPicNums == 1 { + header.AbsDiffPicNumMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") + } + } else if header.ModificationOfPicNums == 2 { + header.LongTermPicNum, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LongTermPicNum") + } + } + } + } + + } + if header.SliceType%5 == 1 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read RefPicListModificationFlagL1") + } + header.RefPicListModificationFlagL1 = b == 1 + + if header.RefPicListModificationFlagL1 { + for header.ModificationOfPicNums != 3 { + header.ModificationOfPicNums, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") + } + + if header.ModificationOfPicNums == 0 || header.ModificationOfPicNums == 1 { + header.AbsDiffPicNumMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") + } + } else if header.ModificationOfPicNums == 2 { + header.LongTermPicNum, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LongTermPicNum") + } + } + } + } + } + // refPicListModification() + } + + if (pps.WeightedPred && (sliceType == "P" || sliceType == "SP")) || (pps.WeightedBipred == 1 && sliceType == "B") { + // predWeightTable() + header.LumaLog2WeightDenom, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaLog2WeightDenom") + } + + if header.ChromaArrayType != 0 { + header.ChromaLog2WeightDenom, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaLog2WeightDenom") + } + } + for i := 0; i <= header.NumRefIdxL0ActiveMinus1; i++ { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LumaWeightL0Flag") + } + header.LumaWeightL0Flag = b == 1 + + if header.LumaWeightL0Flag { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaWeightL0") + } + header.LumaWeightL0 = append(header.LumaWeightL0, se) + + se, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaOffsetL0") + } + header.LumaOffsetL0 = append(header.LumaOffsetL0, se) + } + if header.ChromaArrayType != 0 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ChromaWeightL0Flag") + } + header.ChromaWeightL0Flag = b == 1 + + if header.ChromaWeightL0Flag { + header.ChromaWeightL0 = append(header.ChromaWeightL0, []int{}) + header.ChromaOffsetL0 = append(header.ChromaOffsetL0, []int{}) + for j := 0; j < 2; j++ { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaWeightL0") + } + header.ChromaWeightL0[i] = append(header.ChromaWeightL0[i], se) + + se, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaOffsetL0") + } + header.ChromaOffsetL0[i] = append(header.ChromaOffsetL0[i], se) + } + } + } + } + if header.SliceType%5 == 1 { + for i := 0; i <= header.NumRefIdxL1ActiveMinus1; i++ { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LumaWeightL1Flag") + } + header.LumaWeightL1Flag = b == 1 + + if header.LumaWeightL1Flag { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaWeightL1") + } + header.LumaWeightL1 = append(header.LumaWeightL1, se) + + se, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaOffsetL1") + } + header.LumaOffsetL1 = append(header.LumaOffsetL1, se) + } + if header.ChromaArrayType != 0 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ChromaWeightL1Flag") + } + header.ChromaWeightL1Flag = b == 1 + + if header.ChromaWeightL1Flag { + header.ChromaWeightL1 = append(header.ChromaWeightL1, []int{}) + header.ChromaOffsetL1 = append(header.ChromaOffsetL1, []int{}) + for j := 0; j < 2; j++ { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaWeightL1") + } + header.ChromaWeightL1[i] = append(header.ChromaWeightL1[i], se) + + se, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaOffsetL1") + } + header.ChromaOffsetL1[i] = append(header.ChromaOffsetL1[i], se) + } + } + } + } + } + } // end predWeightTable + if nalUnit.RefIdc != 0 { + // devRefPicMarking() + if idrPic { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read NoOutputOfPriorPicsFlag") + } + header.NoOutputOfPriorPicsFlag = b == 1 + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LongTermReferenceFlag") + } + header.LongTermReferenceFlag = b == 1 + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read AdaptiveRefPicMarkingModeFlag") + } + header.AdaptiveRefPicMarkingModeFlag = b == 1 + + if header.AdaptiveRefPicMarkingModeFlag { + header.MemoryManagementControlOperation, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") + } + for header.MemoryManagementControlOperation != 0 { + if header.MemoryManagementControlOperation == 1 || header.MemoryManagementControlOperation == 3 { + header.DifferenceOfPicNumsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") + } + } + if header.MemoryManagementControlOperation == 2 { + header.LongTermPicNum, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LongTermPicNum") + } + } + if header.MemoryManagementControlOperation == 3 || header.MemoryManagementControlOperation == 6 { + header.LongTermFrameIdx, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LongTermFrameIdx") + } + } + if header.MemoryManagementControlOperation == 4 { + header.MaxLongTermFrameIdxPlus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxLongTermFrameIdxPlus1") + } + } + } + } + } // end decRefPicMarking + } + if pps.EntropyCodingMode == 1 && sliceType != "I" && sliceType != "SI" { + header.CabacInit, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse CabacInit") + } + } + header.SliceQpDelta, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceQpDelta") + } + + if sliceType == "SP" || sliceType == "SI" { + if sliceType == "SP" { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SpForSwitch") + } + header.SpForSwitch = b == 1 + } + header.SliceQsDelta, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceQsDelta") + } + } + if pps.DeblockingFilterControlPresent { + header.DisableDeblockingFilter, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse DisableDeblockingFilter") + } + + if header.DisableDeblockingFilter != 1 { + header.SliceAlphaC0OffsetDiv2, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceAlphaC0OffsetDiv2") + } + + header.SliceBetaOffsetDiv2, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceBetaOffsetDiv2") + } + } + } + if pps.NumSliceGroupsMinus1 > 0 && pps.SliceGroupMapType >= 3 && pps.SliceGroupMapType <= 5 { + b, err := br.ReadBits(int(math.Ceil(math.Log2(float64(pps.PicSizeInMapUnitsMinus1/pps.SliceGroupChangeRateMinus1 + 1))))) + if err != nil { + return nil, errors.Wrap(err, "could not read SliceGruopChangeCycle") + } + header.SliceGroupChangeCycle = int(b) + } + + sliceContext := &SliceContext{ + NalUnit: nalUnit, + SPS: sps, + PPS: pps, + Slice: &Slice{ + Header: &header, + }, + } + sliceContext.Slice.Data, err = NewSliceData(sliceContext, br) + if err != nil { + return nil, errors.Wrap(err, "could not create slice data") + } + if showPacket { + debugPacket("debug: Header", sliceContext.Slice.Header) + debugPacket("debug: Data", sliceContext.Slice.Data) + } + return sliceContext, nil +} diff --git a/codec/h264/decode/slice_test.go b/codec/h264/decode/slice_test.go new file mode 100644 index 00000000..98b052ef --- /dev/null +++ b/codec/h264/decode/slice_test.go @@ -0,0 +1,49 @@ +package h264 + +import "testing" + +var subWidthCTests = []struct { + in SPS + want int +}{ + {SPS{}, 17}, + {SPS{ChromaFormat: 0}, 17}, + {SPS{ChromaFormat: 1}, 2}, + {SPS{ChromaFormat: 2}, 2}, + {SPS{ChromaFormat: 3}, 1}, + {SPS{ChromaFormat: 3, UseSeparateColorPlane: true}, 17}, + {SPS{ChromaFormat: 999}, 17}, +} + +// TestSubWidthC tests that the correct SubWidthC is returned given +// SPS inputs with various chroma formats. +func TestSubWidthC(t *testing.T) { + for _, tt := range subWidthCTests { + if got := SubWidthC(&tt.in); got != tt.want { + t.Errorf("SubWidthC(%#v) = %d, want %d", tt.in, got, tt.want) + } + } +} + +var subHeightCTests = []struct { + in SPS + want int +}{ + {SPS{}, 17}, + {SPS{ChromaFormat: 0}, 17}, + {SPS{ChromaFormat: 1}, 2}, + {SPS{ChromaFormat: 2}, 1}, + {SPS{ChromaFormat: 3}, 1}, + {SPS{ChromaFormat: 3, UseSeparateColorPlane: true}, 17}, + {SPS{ChromaFormat: 999}, 17}, +} + +// TestSubHeightC tests that the correct SubHeightC is returned given +// SPS inputs with various chroma formats. +func TestSubHeightC(t *testing.T) { + for _, tt := range subHeightCTests { + if got := SubHeightC(&tt.in); got != tt.want { + t.Errorf("SubHeight(%#v) = %d, want %d", tt.in, got, tt.want) + } + } +} diff --git a/codec/h264/decode/sps.go b/codec/h264/decode/sps.go new file mode 100644 index 00000000..20d93c99 --- /dev/null +++ b/codec/h264/decode/sps.go @@ -0,0 +1,690 @@ +package h264 + +import ( + "bytes" + "fmt" + "strings" + + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +// Specification Page 43 7.3.2.1.1 +// Range is always inclusive +// XRange is always exclusive +type SPS struct { + // 8 bits + Profile int + // 6 bits + Constraint0, Constraint1 int + Constraint2, Constraint3 int + Constraint4, Constraint5 int + // 2 bit reserved 0 bits + // 8 bits + Level int + // Range 0 - 31 ; 6 bits + ID int + ChromaFormat int + UseSeparateColorPlane bool + BitDepthLumaMinus8 int + BitDepthChromaMinus8 int + QPrimeYZeroTransformBypass bool + SeqScalingMatrixPresent bool + // Delta is (0-12)-1 ; 4 bits + SeqScalingList []bool // se + // Range 0 - 12; 4 bits + Log2MaxFrameNumMinus4 int + // Range 0 - 2; 2 bits + PicOrderCountType int + // Range 0 - 12; 4 bits + Log2MaxPicOrderCntLSBMin4 int + DeltaPicOrderAlwaysZero bool + // Range (-2^31)+1 to (2^31)-1 ; 31 bits + OffsetForNonRefPic int // Value - 1 (se) + // Range (-2^31)+1 to (2^31)-1 ; 31 bits + OffsetForTopToBottomField int // Value - 1 (se) + // Range 0 - 255 ; 8 bits + NumRefFramesInPicOrderCntCycle int + // Range (-2^31)+1 to (2^31)-1 ; 31 bits + OffsetForRefFrameList []int // Value - 1 ([]se) + // Range 0 - MaxDpbFrames + MaxNumRefFrames int + GapsInFrameNumValueAllowed bool + // Page 77 + PicWidthInMbsMinus1 int + // Page 77 + PicHeightInMapUnitsMinus1 int + FrameMbsOnly bool + MBAdaptiveFrameField bool + Direct8x8Inference bool + FrameCropping bool + FrameCropLeftOffset int + FrameCropRightOffset int + FrameCropTopOffset int + FrameCropBottomOffset int + VuiParametersPresent bool + VuiParameters []int + AspectRatioInfoPresent bool + AspectRatio int + SarWidth int + SarHeight int + OverscanInfoPresent bool + OverscanAppropriate bool + VideoSignalTypePresent bool + VideoFormat int + VideoFullRange bool + ColorDescriptionPresent bool + ColorPrimaries int + TransferCharacteristics int + MatrixCoefficients int + ChromaLocInfoPresent bool + ChromaSampleLocTypeTopField int + ChromaSampleLocTypeBottomField int + CpbCntMinus1 int + BitRateScale int + CpbSizeScale int + BitRateValueMinus1 []int + Cbr []bool + InitialCpbRemovalDelayLengthMinus1 int + CpbRemovalDelayLengthMinus1 int + CpbSizeValueMinus1 []int + DpbOutputDelayLengthMinus1 int + TimeOffsetLength int + TimingInfoPresent bool + NumUnitsInTick int + TimeScale int + NalHrdParametersPresent bool + FixedFrameRate bool + VclHrdParametersPresent bool + LowHrdDelay bool + PicStructPresent bool + BitstreamRestriction bool + MotionVectorsOverPicBoundaries bool + MaxBytesPerPicDenom int + MaxBitsPerMbDenom int + Log2MaxMvLengthHorizontal int + Log2MaxMvLengthVertical int + MaxDecFrameBuffering int + MaxNumReorderFrames int +} + +var ( + DefaultScalingMatrix4x4 = [][]int{ + {6, 13, 20, 28, 13, 20, 28, 32, 20, 28, 32, 37, 28, 32, 37, 42}, + {10, 14, 20, 24, 14, 20, 24, 27, 20, 24, 27, 30, 24, 27, 30, 34}, + } + + DefaultScalingMatrix8x8 = [][]int{ + {6, 10, 13, 16, 18, 23, 25, 27, + 10, 11, 16, 18, 23, 25, 27, 29, + 13, 16, 18, 23, 25, 27, 29, 31, + 16, 18, 23, 25, 27, 29, 31, 33, + 18, 23, 25, 27, 29, 31, 33, 36, + 23, 25, 27, 29, 31, 33, 36, 38, + 25, 27, 29, 31, 33, 36, 38, 40, + 27, 29, 31, 33, 36, 38, 40, 42}, + {9, 13, 15, 17, 19, 21, 22, 24, + 13, 13, 17, 19, 21, 22, 24, 25, + 15, 17, 19, 21, 22, 24, 25, 27, + 17, 19, 21, 22, 24, 25, 27, 28, + 19, 21, 22, 24, 25, 27, 28, 30, + 21, 22, 24, 25, 27, 28, 30, 32, + 22, 24, 25, 27, 28, 30, 32, 33, + 24, 25, 27, 28, 30, 32, 33, 35}, + } + Default4x4IntraList = []int{6, 13, 13, 20, 20, 20, 38, 38, 38, 38, 32, 32, 32, 37, 37, 42} + Default4x4InterList = []int{10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34} + Default8x8IntraList = []int{ + 6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, + 23, 23, 23, 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, + 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31, + 31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42} + Default8x8InterList = []int{ + 9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, + 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24, + 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, + 27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35} + ScalingList4x4 = map[int][]int{ + 0: Default4x4IntraList, + 1: Default4x4IntraList, + 2: Default4x4IntraList, + 3: Default4x4InterList, + 4: Default4x4InterList, + 5: Default4x4InterList, + 6: Default8x8IntraList, + 7: Default8x8InterList, + 8: Default8x8IntraList, + 9: Default8x8InterList, + 10: Default8x8IntraList, + 11: Default8x8InterList, + } + ScalingList8x8 = ScalingList4x4 +) + +func isInList(l []int, term int) bool { + for _, m := range l { + if m == term { + return true + } + } + return false +} +func debugPacket(name string, packet interface{}) { + logger.Printf("debug: %s packet\n", name) + for _, line := range strings.Split(fmt.Sprintf("%+v", packet), " ") { + logger.Printf("debug: \t%#v\n", line) + } +} +func scalingList(b *bits.BitReader, scalingList []int, sizeOfScalingList int, defaultScalingMatrix []int) error { + lastScale := 8 + nextScale := 8 + for i := 0; i < sizeOfScalingList; i++ { + if nextScale != 0 { + deltaScale, err := readSe(nil) + if err != nil { + return errors.Wrap(err, "could not parse deltaScale") + } + nextScale = (lastScale + deltaScale + 256) % 256 + if i == 0 && nextScale == 0 { + // Scaling list should use the default list for this point in the matrix + _ = defaultScalingMatrix + } + } + if nextScale == 0 { + scalingList[i] = lastScale + } else { + scalingList[i] = nextScale + } + lastScale = scalingList[i] + } + return nil +} +func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { + logger.Printf("debug: SPS RBSP %d bytes %d bits\n", len(rbsp), len(rbsp)*8) + logger.Printf("debug: \t%#v\n", rbsp[0:8]) + sps := SPS{} + br := bits.NewBitReader(bytes.NewReader(rbsp)) + var err error + hrdParameters := func() error { + sps.CpbCntMinus1, err = readUe(nil) + if err != nil { + return errors.Wrap(err, "could not parse CpbCntMinus1") + } + + err := readFields(br, []field{ + {&sps.BitRateScale, "BitRateScale", 4}, + {&sps.CpbSizeScale, "CpbSizeScale", 4}, + }) + if err != nil { + return err + } + + // SchedSelIdx E1.2 + for sseli := 0; sseli <= sps.CpbCntMinus1; sseli++ { + ue, err := readUe(nil) + if err != nil { + return errors.Wrap(err, "could not parse BitRateValueMinus1") + } + sps.BitRateValueMinus1 = append(sps.BitRateValueMinus1, ue) + + ue, err = readUe(nil) + if err != nil { + return errors.Wrap(err, "could not parse CpbSizeValueMinus1") + } + sps.CpbSizeValueMinus1 = append(sps.CpbSizeValueMinus1, ue) + + if v, _ := br.ReadBits(1); v == 1 { + sps.Cbr = append(sps.Cbr, true) + } else { + sps.Cbr = append(sps.Cbr, false) + } + + err = readFields(br, + []field{ + {&sps.InitialCpbRemovalDelayLengthMinus1, "InitialCpbRemovalDelayLengthMinus1", 5}, + {&sps.CpbRemovalDelayLengthMinus1, "CpbRemovalDelayLengthMinus1", 5}, + {&sps.DpbOutputDelayLengthMinus1, "DpbOutputDelayLengthMinus1", 5}, + {&sps.TimeOffsetLength, "TimeOffsetLength", 5}, + }, + ) + if err != nil { + return err + } + } + return nil + } + + err = readFields(br, + []field{ + {&sps.Profile, "ProfileIDC", 8}, + {&sps.Constraint0, "Constraint0", 1}, + {&sps.Constraint1, "Constraint1", 1}, + {&sps.Constraint2, "Constraint2", 1}, + {&sps.Constraint3, "Constraint3", 1}, + {&sps.Constraint4, "Constraint4", 1}, + {&sps.Constraint5, "Constraint5", 1}, + }, + ) + + _, err = br.ReadBits(2) + if err != nil { + return nil, errors.Wrap(err, "could not read ReservedZeroBits") + } + + b, err := br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read Level") + } + sps.Level = int(b) + + // sps.ID = b.NextField("SPSID", 6) // proper + sps.ID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ID") + } + + sps.ChromaFormat, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaFormat") + } + + // This should be done only for certain ProfileIDC: + isProfileIDC := []int{100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135} + // SpecialProfileCase1 + if isInList(isProfileIDC, sps.Profile) { + if sps.ChromaFormat == chroma444 { + // TODO: should probably deal with error here. + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read UseSeparateColorPlaneFlag") + } + sps.UseSeparateColorPlane = b == 1 + } + + sps.BitDepthLumaMinus8, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse BitDepthLumaMinus8") + } + + sps.BitDepthChromaMinus8, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse BitDepthChromaMinus8") + } + + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read QPrimeYZeroTransformBypass") + } + sps.QPrimeYZeroTransformBypass = b == 1 + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SeqScalingMatrixPresent") + } + sps.SeqScalingMatrixPresent = b == 1 + + if sps.SeqScalingMatrixPresent { + max := 12 + if sps.ChromaFormat != chroma444 { + max = 8 + } + logger.Printf("debug: \tbuilding Scaling matrix for %d elements\n", max) + for i := 0; i < max; i++ { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SeqScalingList") + } + sps.SeqScalingList = append(sps.SeqScalingList, b == 1) + + if sps.SeqScalingList[i] { + if i < 6 { + scalingList( + br, + ScalingList4x4[i], + 16, + DefaultScalingMatrix4x4[i]) + // 4x4: Page 75 bottom + } else { + // 8x8 Page 76 top + scalingList( + br, + ScalingList8x8[i], + 64, + DefaultScalingMatrix8x8[i-6]) + } + } + } + } + } // End SpecialProfileCase1 + + // showSPS() + // return sps + // Possibly wrong due to no scaling list being built + sps.Log2MaxFrameNumMinus4, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse Log2MaxFrameNumMinus4") + } + + sps.PicOrderCountType, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PicOrderCountType") + } + + if sps.PicOrderCountType == 0 { + sps.Log2MaxPicOrderCntLSBMin4, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse Log2MaxPicOrderCntLSBMin4") + } + } else if sps.PicOrderCountType == 1 { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read DeltaPicOrderAlwaysZero") + } + sps.DeltaPicOrderAlwaysZero = b == 1 + + sps.OffsetForNonRefPic, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse OffsetForNonRefPic") + } + + sps.OffsetForTopToBottomField, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse OffsetForTopToBottomField") + } + + sps.NumRefFramesInPicOrderCntCycle, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse NumRefFramesInPicOrderCntCycle") + } + + for i := 0; i < sps.NumRefFramesInPicOrderCntCycle; i++ { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse OffsetForRefFrameList") + } + sps.OffsetForRefFrameList = append( + sps.OffsetForRefFrameList, + se) + } + + } + + sps.MaxNumRefFrames, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxNumRefFrames") + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read GapsInFrameNumValueAllowed") + } + sps.GapsInFrameNumValueAllowed = b == 1 + + sps.PicWidthInMbsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PicWidthInMbsMinus1") + } + + sps.PicHeightInMapUnitsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PicHeightInMapUnitsMinus1") + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read FrameMbsOnly") + } + sps.FrameMbsOnly = b == 1 + + if !sps.FrameMbsOnly { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read MBAdaptiveFrameField") + } + sps.MBAdaptiveFrameField = b == 1 + } + + err = readFlags(br, []flag{ + {&sps.Direct8x8Inference, "Direct8x8Inference"}, + {&sps.FrameCropping, "FrameCropping"}, + }) + if err != nil { + return nil, err + } + + if sps.FrameCropping { + sps.FrameCropLeftOffset, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FrameCropLeftOffset") + } + + sps.FrameCropRightOffset, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FrameCropRightOffset") + } + + sps.FrameCropTopOffset, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FrameCropTopOffset") + } + + sps.FrameCropBottomOffset, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FrameCropBottomOffset") + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read VuiParametersPresent") + } + sps.VuiParametersPresent = b == 1 + + if sps.VuiParametersPresent { + // vui_parameters + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read AspectRatioInfoPresent") + } + sps.AspectRatioInfoPresent = b == 1 + + if sps.AspectRatioInfoPresent { + b, err = br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read AspectRatio") + } + sps.AspectRatio = int(b) + + EXTENDED_SAR := 999 + if sps.AspectRatio == EXTENDED_SAR { + b, err = br.ReadBits(16) + if err != nil { + return nil, errors.Wrap(err, "could not read SarWidth") + } + sps.SarWidth = int(b) + + b, err = br.ReadBits(16) + if err != nil { + return nil, errors.Wrap(err, "could not read SarHeight") + } + sps.SarHeight = int(b) + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read OverscanInfoPresent") + } + sps.OverscanInfoPresent = b == 1 + + if sps.OverscanInfoPresent { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read OverscanAppropriate") + } + sps.OverscanAppropriate = b == 1 + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read VideoSignalTypePresent") + } + sps.VideoSignalTypePresent = b == 1 + + if sps.VideoSignalTypePresent { + b, err = br.ReadBits(3) + if err != nil { + return nil, errors.Wrap(err, "could not read VideoFormat") + } + sps.VideoFormat = int(b) + } + + if sps.VideoSignalTypePresent { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read VideoFullRange") + } + sps.VideoFullRange = b == 1 + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ColorDescriptionPresent") + } + sps.ColorDescriptionPresent = b == 1 + + if sps.ColorDescriptionPresent { + err = readFields(br, + []field{ + {&sps.ColorPrimaries, "ColorPrimaries", 8}, + {&sps.TransferCharacteristics, "TransferCharacteristics", 8}, + {&sps.MatrixCoefficients, "MatrixCoefficients", 8}, + }, + ) + if err != nil { + return nil, err + } + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ChromaLocInfoPresent") + } + sps.ChromaLocInfoPresent = b == 1 + + if sps.ChromaLocInfoPresent { + sps.ChromaSampleLocTypeTopField, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaSampleLocTypeTopField") + } + + sps.ChromaSampleLocTypeBottomField, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaSampleLocTypeBottomField") + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read TimingInfoPresent") + } + sps.TimingInfoPresent = b == 1 + + if sps.TimingInfoPresent { + err := readFields(br, []field{ + {&sps.NumUnitsInTick, "NumUnitsInTick", 32}, + {&sps.TimeScale, "TimeScale", 32}, + }) + if err != nil { + return nil, err + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read FixedFrameRate") + } + sps.FixedFrameRate = b == 1 + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read NalHrdParametersPresent") + } + sps.NalHrdParametersPresent = b == 1 + + if sps.NalHrdParametersPresent { + err = hrdParameters() + if err != nil { + return nil, errors.Wrap(err, "could not get hrdParameters") + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read VclHrdParametersPresent") + } + sps.VclHrdParametersPresent = b == 1 + + if sps.VclHrdParametersPresent { + err = hrdParameters() + if err != nil { + return nil, errors.Wrap(err, "could not get hrdParameters") + } + } + if sps.NalHrdParametersPresent || sps.VclHrdParametersPresent { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LowHrdDelay") + } + sps.LowHrdDelay = b == 1 + } + + err := readFlags(br, []flag{ + {&sps.PicStructPresent, "PicStructPresent"}, + {&sps.BitstreamRestriction, "BitStreamRestriction"}, + }) + + if sps.BitstreamRestriction { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read MotionVectorsOverPicBoundaries") + } + sps.MotionVectorsOverPicBoundaries = b == 1 + + sps.MaxBytesPerPicDenom, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxBytesPerPicDenom") + } + + sps.MaxBitsPerMbDenom, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxBitsPerMbDenom") + } + + sps.Log2MaxMvLengthHorizontal, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse Log2MaxMvLengthHorizontal") + } + + sps.Log2MaxMvLengthVertical, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse Log2MaxMvLengthVertical") + } + + sps.MaxNumReorderFrames, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxNumReorderFrames") + } + + sps.MaxDecFrameBuffering, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxDecFrameBuffering") + } + } + + } // End VuiParameters Annex E.1.1 + if showPacket { + debugPacket("SPS", sps) + } + return &sps, nil +} diff --git a/codec/h264/decode/stateTransxTab.go b/codec/h264/decode/stateTransxTab.go new file mode 100644 index 00000000..31f15fcd --- /dev/null +++ b/codec/h264/decode/stateTransxTab.go @@ -0,0 +1,74 @@ +package h264 + +type StateTransx struct { + TransIdxLPS, TransIdxMPS int +} + +// 9-45 +// [pStateIdx]{TransIdxLPS, TransIdxMPS} +var stateTransxTab = map[int]StateTransx{ + 0: {0, 1}, + 1: {0, 2}, + 2: {1, 3}, + 3: {2, 4}, + 4: {2, 5}, + 5: {4, 6}, + 6: {4, 7}, + 7: {5, 8}, + 8: {6, 9}, + 9: {7, 10}, + 10: {8, 11}, + 11: {9, 12}, + 12: {9, 13}, + 13: {11, 14}, + 14: {11, 15}, + 15: {12, 16}, + 16: {13, 17}, + 17: {13, 18}, + 18: {15, 19}, + 19: {15, 20}, + 20: {16, 21}, + 21: {16, 22}, + 22: {18, 23}, + 23: {18, 24}, + 24: {19, 25}, + 25: {19, 26}, + 26: {21, 27}, + 27: {21, 28}, + 28: {22, 29}, + 29: {22, 30}, + 30: {23, 31}, + 31: {24, 32}, + 32: {24, 33}, + 33: {25, 34}, + 34: {26, 35}, + 35: {26, 36}, + 36: {27, 37}, + 37: {27, 38}, + 38: {28, 39}, + 39: {29, 40}, + 40: {29, 41}, + 41: {30, 42}, + 42: {30, 43}, + 43: {30, 44}, + 44: {31, 45}, + 45: {32, 46}, + 46: {32, 47}, + 47: {33, 48}, + 48: {33, 49}, + 49: {33, 50}, + 50: {34, 51}, + 51: {34, 52}, + 52: {35, 53}, + 53: {35, 54}, + 54: {35, 55}, + 55: {36, 56}, + 56: {36, 57}, + 57: {36, 58}, + 58: {37, 59}, + 59: {37, 61}, + 60: {37, 61}, + 61: {38, 62}, + 62: {38, 62}, + 63: {63, 63}, +}