Merged in h264decode-move (pull request #212)

codec/h264: added mrmod h264decode pkg to our av/codec/h264 dir

Approved-by: kortschak <dan@kortschak.io>
Approved-by: Alan Noble <anoble@gmail.com>
This commit is contained in:
Saxon Milton 2019-07-19 02:50:49 +00:00
commit 6880cc3298
25 changed files with 5775 additions and 0 deletions

3
codec/h264/decode/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.*.swp
*.mp4
*.h264

View File

@ -0,0 +1,2 @@
# Default owners for everything in this repo.
* @scruzin @saxon-milton

339
codec/h264/decode/LICENSE Normal file
View File

@ -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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@ -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.

View File

@ -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 <saxon@ausocean.org>, 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
}

View File

@ -0,0 +1,311 @@
/*
DESCRIPTION
bitreader_test.go provides testing for functionality defined in bitreader.go.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>, 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)
}
}
}

691
codec/h264/decode/cabac.go Normal file
View File

@ -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<<uint(bitDepthY))-1, x)
}
func Clipc(x, bitDepthC int) int {
return Clip3(0, (1<<uint(bitDepthC))-1, x)
}
// 5-5
func Clip3(x, y, z int) int {
if z < x {
return x
}
if z > 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
}

View File

@ -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)
}
}
}

View File

@ -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
}

8
codec/h264/decode/go.mod Normal file
View File

@ -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
)

4
codec/h264/decode/go.sum Normal file
View File

@ -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=

170
codec/h264/decode/mbtype.go Normal file
View File

@ -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
}

440
codec/h264/decode/mnvars.go Normal file
View File

@ -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
}

View File

@ -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
}

161
codec/h264/decode/parse.go Normal file
View File

@ -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 <saxon@ausocean.org>, The Australian Ocean Laboratory (AusOcean)
mrmod <mcmoranbjr@gmail.com>
*/
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},
},
}

View File

@ -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 <saxon@ausocean.org>, 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)
}
}
}

239
codec/h264/decode/pps.go Normal file
View File

@ -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
}

View File

@ -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
}

86
codec/h264/decode/rbsp.go Normal file
View File

@ -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
}

230
codec/h264/decode/read.go Normal file
View File

@ -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
}

View File

@ -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()
}

1369
codec/h264/decode/slice.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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)
}
}
}

690
codec/h264/decode/sps.go Normal file
View File

@ -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
}

View File

@ -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},
}