Merge branch 'master' into revid-audio

This commit is contained in:
Trek H 2019-08-02 23:32:21 +09:30
commit 3f29f0c84e
40 changed files with 7944 additions and 174 deletions

View File

@ -288,7 +288,7 @@ func run(cfg revid.Config) {
ns, err := netsender.New(log, nil, readPin, nil)
if err != nil {
log.Log(logger.Fatal, pkg+"could not initialise netsender client")
log.Log(logger.Fatal, pkg+"could not initialise netsender client: "+err.Error())
}
var vs int

View File

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

339
codec/h264/h264dec/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,175 @@
/*
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
}
// Off returns the current offset from the starting bit of the current byte.
func (br *BitReader) Off() int {
return br.bits
}
// 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/h264dec/cabac.go Normal file
View File

@ -0,0 +1,691 @@
package h264dec
import (
"bitbucket.org/ausocean/av/codec/h264/h264dec/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 h264dec
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,91 @@
package h264dec
// 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
naluTypeSliceLayerExtRBSP = 20
naluTypeSliceLayerExtRBSP2 = 21
)
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
}

View File

@ -0,0 +1,41 @@
/*
DESCRIPTION
helpers.go provides general helper utilities.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>, The Australian Ocean Laboratory (AusOcean)
*/
package h264dec
import "errors"
// binToSlice is a helper function to convert a string of binary into a
// corresponding byte slice, e.g. "0100 0001 1000 1100" => {0x41,0x8c}.
// Spaces in the string are ignored.
func binToSlice(s string) ([]byte, error) {
var (
a byte = 0x80
cur byte
bytes []byte
)
for _, c := range s {
switch c {
case ' ':
continue
case '1':
cur |= a
case '0':
default:
return nil, errors.New("invalid binary string")
}
a >>= 1
if a == 0 {
bytes = append(bytes, cur)
cur = 0
a = 0x80
}
}
return bytes, nil
}

View File

@ -0,0 +1,178 @@
package h264dec
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")
errSliceType = errors.New("bad sliceType")
)
// 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 naMbPartPredMode, errNaMode
case "SI":
if mbType != 0 {
return naMbPartPredMode, errNaMode
}
return intra4x4, nil
case "P":
fallthrough
case "SP":
if mbType >= 0 && mbType < 3 {
return predL0, nil
} else if mbType == 3 || mbType == 4 {
return naMbPartPredMode, errNaMode
} else {
return predL0, nil
}
case "B":
switch mbType {
case 0:
return direct, nil
case 3:
return biPred, 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 naMbPartPredMode, errNaMode
default:
if mbType > 15 && mbType < 22 {
return biPred, nil
}
return direct, nil
}
default:
return naMbPartPredMode, errSliceType
}
}
return naMbPartPredMode, errPartition
}

View File

@ -0,0 +1,112 @@
/*
NAME
parse.go
DESCRIPTION
mbtype_test.go provides testing for functions provided in mbtype.go.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>, The Australian Ocean Laboratory (AusOcean)
*/
package h264dec
import (
"testing"
)
func TestMbPartPredMode(t *testing.T) {
tests := []struct {
sliceType string
mbType int
data *SliceData
want mbPartPredMode
err error
}{
// Table 7-11 (I-slices).
0: {"I", 0, &SliceData{TransformSize8x8Flag: false}, intra4x4, nil},
1: {"I", 0, &SliceData{TransformSize8x8Flag: true}, intra8x8, nil},
2: {"I", 1, nil, intra16x16, nil},
3: {"I", 2, nil, intra16x16, nil},
4: {"I", 3, nil, intra16x16, nil},
5: {"I", 4, nil, intra16x16, nil},
6: {"I", 5, nil, intra16x16, nil},
7: {"I", 6, nil, intra16x16, nil},
8: {"I", 7, nil, intra16x16, nil},
9: {"I", 8, nil, intra16x16, nil},
10: {"I", 9, nil, intra16x16, nil},
11: {"I", 10, nil, intra16x16, nil},
12: {"I", 11, nil, intra16x16, nil},
13: {"I", 12, nil, intra16x16, nil},
14: {"I", 13, nil, intra16x16, nil},
15: {"I", 14, nil, intra16x16, nil},
16: {"I", 15, nil, intra16x16, nil},
17: {"I", 16, nil, intra16x16, nil},
18: {"I", 17, nil, intra16x16, nil},
19: {"I", 18, nil, intra16x16, nil},
20: {"I", 19, nil, intra16x16, nil},
21: {"I", 20, nil, intra16x16, nil},
22: {"I", 21, nil, intra16x16, nil},
23: {"I", 22, nil, intra16x16, nil},
24: {"I", 23, nil, intra16x16, nil},
25: {"I", 24, nil, intra16x16, nil},
26: {"I", 25, nil, naMbPartPredMode, errNaMode},
// Table 7-12 (SI-slices).
27: {"SI", 0, nil, intra4x4, nil},
// Table 7-13 (SP-slices).
28: {"SP", 0, nil, predL0, nil},
29: {"SP", 1, nil, predL0, nil},
30: {"SP", 2, nil, predL0, nil},
31: {"SP", 3, nil, naMbPartPredMode, errNaMode},
32: {"SP", 4, nil, naMbPartPredMode, errNaMode},
// Table 7-14 (B-slices).
33: {"B", 0, nil, direct, nil},
34: {"B", 1, nil, predL0, nil},
35: {"B", 2, nil, predL1, nil},
36: {"B", 3, nil, biPred, nil},
37: {"B", 4, nil, predL0, nil},
38: {"B", 5, nil, predL0, nil},
39: {"B", 6, nil, predL1, nil},
40: {"B", 7, nil, predL1, nil},
41: {"B", 8, nil, predL0, nil},
42: {"B", 9, nil, predL0, nil},
43: {"B", 10, nil, predL1, nil},
44: {"B", 11, nil, predL1, nil},
45: {"B", 12, nil, predL0, nil},
46: {"B", 13, nil, predL0, nil},
47: {"B", 14, nil, predL1, nil},
48: {"B", 15, nil, predL1, nil},
49: {"B", 16, nil, biPred, nil},
50: {"B", 17, nil, biPred, nil},
51: {"B", 18, nil, biPred, nil},
52: {"B", 19, nil, biPred, nil},
53: {"B", 20, nil, biPred, nil},
54: {"B", 21, nil, biPred, nil},
55: {"B", 22, nil, naMbPartPredMode, errNaMode},
// Test some weird cases where we expect error.
56: {"O", 0, nil, naMbPartPredMode, errSliceType},
57: {"I", 26, nil, naMbPartPredMode, errNaMode},
58: {"I", -1, nil, naMbPartPredMode, errNaMode},
59: {"SI", 1, nil, naMbPartPredMode, errNaMode},
// Cases for inferred mbtype.
60: {"SP", 5, nil, predL0, nil},
61: {"SP", -1, nil, predL0, nil},
62: {"B", -1, nil, direct, nil},
63: {"B", 23, nil, direct, nil},
}
for i, test := range tests {
m, err := MbPartPredMode(test.data, test.sliceType, test.mbType, 0)
if err != test.err {
t.Errorf("unexpected error from test %d.\nGot: %v\nWant: %v\n", i, err, test.err)
}
if m != test.want {
t.Errorf("did not get expected result for test %d.\nGot: %v\nWant: %v\n", i, m, test.want)
}
}
}

View File

@ -0,0 +1,440 @@
package h264dec
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,291 @@
/*
DESCRIPTION
nalunit.go provides structures for a NAL unit as well as it's extensions.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>, The Australian Ocean Laboratory (AusOcean)
mrmod <mcmoranbjr@gmail.com>
*/
package h264dec
import (
"fmt"
"io"
"github.com/pkg/errors"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
)
// MVCExtension describes a NAL unit header multiview video coding extension, as
// defined in section H.7.3.1.1 of the specifications.
// Semantics of fields are specified in section H.7.4.1.1.
type MVCExtension struct {
// non_idr_flag, if true indicates that access unit is not IDR.
NonIdrFlag bool
// priority_id, indicates priority of NAL unit. A lower value => higher priority.
PriorityID uint8
// view_id, specifies a view identifier for the unit. Units with identical
// view_id are in the same view.
ViewID uint32
// temporal_id, temporal identifier for the unit.
TemporalID uint8
// anchor_pic_flag, if true access unit is an anchor access unit.
AnchorPicFlag bool
// inter_view_flag, if false current view component not used for inter-view
// prediction elsewhere in access unit.
InterViewFlag bool
// reserved_one_bit, always 1 (ignored by decoders)
ReservedOneBit uint8
}
// NewMVCExtension parses a NAL unit header multiview video coding extension
// from br following the syntax structure specified in section H.7.3.1.1, and
// returns as a new MVCExtension.
func NewMVCExtension(br *bits.BitReader) (*MVCExtension, error) {
e := &MVCExtension{}
r := newFieldReader(br)
e.NonIdrFlag = r.readBits(1) == 1
e.PriorityID = uint8(r.readBits(6))
e.ViewID = uint32(r.readBits(10))
e.TemporalID = uint8(r.readBits(3))
e.AnchorPicFlag = r.readBits(1) == 1
e.InterViewFlag = r.readBits(1) == 1
e.ReservedOneBit = uint8(r.readBits(1))
if r.err() != nil {
return nil, fmt.Errorf("error from fieldReader: %v", r.err())
}
return e, nil
}
// ThreeDAVCExtension describes a NAL unit header 3D advanced video coding
// extension, as defined in section J.7.3.1.1 of the specifications.
// For field semantics see section J.7.4.1.1.
type ThreeDAVCExtension struct {
// view_idx, specifies the order index for the NAL i.e. view_id = view_id[view_idx].
ViewIdx uint8
// dpeth_flag, if true indicates NAL part of a depth view component, otherwise
// a texture view component.
DepthFlag bool
// non_idr_flag, if true indicates that access unit is not IDR.
NonIdrFlag bool
// temporal_id, temporal identifier for the unit.
TemporalID uint8
// anchor_pic_flag, if true access unit is an anchor access unit.
AnchorPicFlag bool
// inter_view_flag, if false current view component not used for inter-view
// prediction elsewhere in access unit.
InterViewFlag bool
}
// NewThreeDAVCExtension parses a NAL unit header 3D advanced video coding
// extension from br following the syntax structure specified in section
// J.7.3.1.1, and returns as a new ThreeDAVCExtension.
func NewThreeDAVCExtension(br *bits.BitReader) (*ThreeDAVCExtension, error) {
e := &ThreeDAVCExtension{}
r := newFieldReader(br)
e.ViewIdx = uint8(r.readBits(8))
e.DepthFlag = r.readBits(1) == 1
e.NonIdrFlag = r.readBits(1) == 1
e.TemporalID = uint8(r.readBits(3))
e.AnchorPicFlag = r.readBits(1) == 1
e.InterViewFlag = r.readBits(1) == 1
if r.err() != nil {
return nil, fmt.Errorf("error from fieldReader: %v", r.err())
}
return e, nil
}
// SVCExtension describes a NAL unit header scalable video coding extension, as
// defined in section G.7.3.1.1 of the specifications.
// For field semantics see section G.7.4.1.1.
type SVCExtension struct {
// idr_flag, if true the current coded picture is an IDR picture when
// dependency_id == max(dependency_id) in the coded picture.
IdrFlag bool
// priority_id, specifies priority identifier for unit.
PriorityID uint8
// no_inter_layer_pred_flag, if true inter-layer prediction can't be used for
// decoding slice.
NoInterLayerPredFlag bool
// dependency_id, specifies a dependency identifier for the NAL.
DependencyID uint8
// quality_id, specifies a quality identifier for the NAL.
QualityID uint8
// temporal_id, specifiesa temporal identifier for the NAL.
TemporalID uint8
// use_ref_base_pic_flag, if true indicates reference base pictures and
// decoded pictures are used as references for inter prediction.
UseRefBasePicFlag bool
// discardable_flag, if true, indicates current NAL is not used for decoding
// dependency representations that are part of the current coded picture or
// any subsequent coded picture in decoding order and have a greater
// dependency_id value than current NAL.
DiscardableFlag bool
// output_flag, affects the decoded picture output and removal processes as
// specified in Annex C.
OutputFlag bool
// reserved_three_2bits, equal to 3. Decoders ignore.
ReservedThree2Bits uint8
}
// NewSVCExtension parses a NAL unit header scalable video coding extension from
// br following the syntax structure specified in section G.7.3.1.1, and returns
// as a new SVCExtension.
func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) {
e := &SVCExtension{}
r := newFieldReader(br)
e.IdrFlag = r.readBits(1) == 1
e.PriorityID = uint8(r.readBits(6))
e.NoInterLayerPredFlag = r.readBits(1) == 1
e.DependencyID = uint8(r.readBits(3))
e.QualityID = uint8(r.readBits(4))
e.TemporalID = uint8(r.readBits(3))
e.UseRefBasePicFlag = r.readBits(1) == 1
e.DiscardableFlag = r.readBits(1) == 1
e.OutputFlag = r.readBits(1) == 1
e.ReservedThree2Bits = uint8(r.readBits(2))
if r.err() != nil {
return nil, fmt.Errorf("error from fieldReader: %v", r.err())
}
return e, nil
}
// NALUnit describes a network abstraction layer unit, as defined in section
// 7.3.1 of the specifications.
// Field semantics are defined in section 7.4.1.
type NALUnit struct {
// forbidden_zero_bit, always 0.
ForbiddenZeroBit uint8
// nal_ref_idc, if not 0 indicates content of NAL contains a sequence parameter
// set, a sequence parameter set extension, a subset sequence parameter set,
// a picture parameter set, a slice of a reference picture, a slice data
// partition of a reference picture, or a prefix NAL preceding a slice of
// a reference picture.
RefIdc uint8
// nal_unit_type, specifies the type of RBSP data contained in the NAL as
// defined in Table 7-1.
Type uint8
// svc_extension_flag, indicates whether a nal_unit_header_svc_extension()
// (G.7.3.1.1) or nal_unit_header_mvc_extension() (H.7.3.1.1) will follow next
// in the syntax structure.
SVCExtensionFlag bool
// avc_3d_extension_flag, for nal_unit_type = 21, indicates that a
// nal_unit_header_mvc_extension() (H.7.3.1.1) or
// nal_unit_header_3davc_extension() (J.7.3.1.1) will follow next in syntax
// structure.
AVC3DExtensionFlag bool
// nal_unit_header_svc_extension() as defined in section G.7.3.1.1.
SVCExtension *SVCExtension
// nal_unit_header_3davc_extension() as defined in section J.7.3.1.1
ThreeDAVCExtension *ThreeDAVCExtension
// nal_unit_header_mvc_extension() as defined in section H.7.3.1.1).
MVCExtension *MVCExtension
// emulation_prevention_three_byte, equal to 0x03 and is discarded by decoder.
EmulationPreventionThreeByte byte
// rbsp_byte, the raw byte sequence payload data for the NAL.
RBSP []byte
}
// NewNALUnit parses a network abstraction layer unit from br following the
// syntax structure specified in section 7.3.1, and returns as a new NALUnit.
func NewNALUnit(br *bits.BitReader) (*NALUnit, error) {
n := &NALUnit{}
r := newFieldReader(br)
n.ForbiddenZeroBit = uint8(r.readBits(1))
n.RefIdc = uint8(r.readBits(2))
n.Type = uint8(r.readBits(5))
var err error
if n.Type == naluTypePrefixNALU || n.Type == naluTypeSliceLayerExtRBSP || n.Type == naluTypeSliceLayerExtRBSP2 {
if n.Type != naluTypeSliceLayerExtRBSP2 {
n.SVCExtensionFlag = r.readBits(1) == 1
} else {
n.AVC3DExtensionFlag = r.readBits(1) == 1
}
if n.SVCExtensionFlag {
n.SVCExtension, err = NewSVCExtension(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse SVCExtension")
}
} else if n.AVC3DExtensionFlag {
n.ThreeDAVCExtension, err = NewThreeDAVCExtension(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse ThreeDAVCExtension")
}
} else {
n.MVCExtension, err = NewMVCExtension(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse MVCExtension")
}
}
}
for moreRBSPData(br) {
next3Bytes, err := br.PeekBits(24)
// If PeekBits cannot get 3 bytes, but there still might be 2 bytes left in
// the source, we will get an io.EOF; we wish to ignore this and continue.
// The call to moreRBSPData will determine when we have reached the end of
// the NAL unit.
if err != nil && errors.Cause(err) != io.EOF {
return nil, errors.Wrap(err, "could not Peek next 3 bytes")
}
if next3Bytes == 0x000003 {
for j := 0; j < 2; j++ {
rbspByte := byte(r.readBits(8))
n.RBSP = append(n.RBSP, byte(rbspByte))
}
// Read Emulation prevention three byte.
n.EmulationPreventionThreeByte = byte(r.readBits(8))
} else {
n.RBSP = append(n.RBSP, byte(r.readBits(8)))
}
}
if r.err() != nil {
return nil, fmt.Errorf("fieldReader error: %v", r.err())
}
return n, nil
}

245
codec/h264/h264dec/parse.go Normal file
View File

@ -0,0 +1,245 @@
/*
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 h264dec
import (
"math"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
"github.com/pkg/errors"
)
// mbPartPredMode represents a macroblock partition prediction mode.
// Modes are defined as consts below. These modes are in section 7.4.5.
type mbPartPredMode int8
const (
intra4x4 mbPartPredMode = iota
intra8x8
intra16x16
predL0
predL1
direct
biPred
inter
naMbPartPredMode
)
// fieldReader provides methods for reading bool and int fields from a
// bits.BitReader with a sticky error that may be checked after a series of
// parsing read calls.
type fieldReader struct {
e error
br *bits.BitReader
}
// newFieldReader returns a new fieldReader.
func newFieldReader(br *bits.BitReader) fieldReader {
return fieldReader{br: br}
}
// readBitsInt returns an int from reading n bits from br. If we have an error
// already, we do not continue with the read.
func (r fieldReader) readBits(n int) uint64 {
if r.e != nil {
return 0
}
var b uint64
b, r.e = r.br.ReadBits(n)
return b
}
// 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 and return as an int. The read does not happen if the fieldReader
// has a non-nil error.
func (r fieldReader) readUe() int {
if r.e != nil {
return 0
}
var i int
i, r.e = readUe(r.br)
return i
}
// 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
// and returns as an int. The read does not happen if the fieldReader
// has a non-nil error.
func (r fieldReader) readTe(x uint) int {
if r.e != nil {
return 0
}
var i int
i, r.e = readTe(r.br, x)
return i
}
// 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 and returns as int. The read does not happen if the fieldReader
// has a non-nil error.
func (r fieldReader) readSe() int {
if r.e != nil {
return 0
}
var i int
i, r.e = readSe(r.br)
return i
}
// 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
// and returns as int. The read does not happen if the fieldReader has a
// non-nil error.
func (r fieldReader) readMe(chromaArrayType uint, mpm mbPartPredMode) int {
if r.e != nil {
return 0
}
var i uint
i, r.e = readMe(r.br, chromaArrayType, mpm)
return int(i)
}
// err returns the fieldReader's error e.
func (r fieldReader) err() error {
return r.e
}
// 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 *bits.BitReader) (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(int(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 *bits.BitReader, 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 *bits.BitReader) (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 *bits.BitReader, 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 h264dec
import (
"bytes"
"testing"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
)
// 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(bits.NewBitReader(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(bits.NewBitReader(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}, // Bit string: 1, codeNum: 0, syntax element val: 0
{[]byte{0x40}, 1}, // Bit string: 010, codeNum: 1, syntax element val: 1
{[]byte{0x60}, -1}, // Bit string: 011, codeNum: 2, syntax element val: -1
{[]byte{0x20}, 2}, // Bit string: 00100, codeNum: 3, syntax element val: 2
{[]byte{0x28}, -2}, // Bit string: 00101, codeNum: 4, syntax element val: -2
{[]byte{0x30}, 3}, // Bit string: 00110, codeNum: 5, syntax element val: 3
{[]byte{0x38}, -3}, // Bit string: 00111, codeNum: 6, syntax element val: -3
}
for i, test := range tests {
got, err := readSe(bits.NewBitReader(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(bits.NewBitReader(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)
}
}
}

229
codec/h264/h264dec/pps.go Normal file
View File

@ -0,0 +1,229 @@
package h264dec
import (
"math"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
"github.com/pkg/errors"
)
// import "strings"
// Specification Page 46 7.3.2.2
type PPS struct {
ID, SPSID int
EntropyCodingMode int
BottomFieldPicOrderInFramePresent bool
NumSliceGroupsMinus1 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(br *bits.BitReader, chromaFormat int) (*PPS, error) {
pps := PPS{}
var err error
pps.ID, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse ID")
}
pps.SPSID, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse NumSliceGroupsMinus1")
}
if pps.NumSliceGroupsMinus1 > 0 {
pps.SliceGroupMapType, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse SliceGroupMapType")
}
if pps.SliceGroupMapType == 0 {
for iGroup := 0; iGroup <= pps.NumSliceGroupsMinus1; iGroup++ {
b, err := readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse RunLengthMinus1")
}
pps.RunLengthMinus1 = append(pps.RunLengthMinus1, b)
}
} else if pps.SliceGroupMapType == 2 {
for iGroup := 0; iGroup < pps.NumSliceGroupsMinus1; iGroup++ {
pps.TopLeft[iGroup], err = readUe(br)
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(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse SliceGroupChangeRateMinus1")
}
} else if pps.SliceGroupMapType == 6 {
pps.PicSizeInMapUnitsMinus1, err = readUe(br)
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(br)
if err != nil {
return nil, errors.New("could not parse NumRefIdxL0DefaultActiveMinus1")
}
pps.NumRefIdxL1DefaultActiveMinus1, err = readUe(br)
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(br)
if err != nil {
return nil, errors.New("could not parse PicInitQpMinus26")
}
pps.PicInitQsMinus26, err = readSe(br)
if err != nil {
return nil, errors.New("could not parse PicInitQsMinus26")
}
pps.ChromaQpIndexOffset, err = readSe(br)
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 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(br)
if err != nil {
return nil, errors.New("could not parse SecondChromaQpIndexOffset")
}
}
moreRBSPData(br)
return &pps, nil
}

View File

@ -0,0 +1,123 @@
/*
DESCRIPTION
pps_test.go provides testing for parsing functionality found in pps.go.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>, The Australian Ocean Laboratory (AusOcean)
*/
package h264dec
import (
"bytes"
"reflect"
"testing"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
)
func TestNewPPS(t *testing.T) {
// TODO: add test with scaling list once we have a test for scalingList func.
tests := []struct {
in string
chromaFormat int
want PPS
}{
{
in: "1" + // ue(v) pic_parameter_set_id = 0
"1" + // ue(v) seq_parameter_set_id = 0
"1" + // u(1) entropy_coding_mode_flag = 1
"0" + // u(1) pic_order_present_flag = 0
"1" + // ue(v) num_slice_groups_minus1 = 0
"1" + // ue(v) num_ref_idx_L0_active_minus1 = 0
"1" + // ue(v) num_ref_idx_L1_active_minus1 = 0
"1" + // u(1) weighted_pred_flag = 1
"00" + // u(2) weighted_bipred_idc = 0
"1" + // se(v) pic_init_qp_minus26 = 0
"1" + // se(v) pic_init_qs_minus26 = 0
"1" + // se(v) chroma_qp_index_offset = 0
"1" + // u(1) deblocking_filter_control_present_flag = 1
"0" + // u(1) constrained_intra_pred_flag = 0
"0" + // u(1) redundant_pic_cnt_present_flag = 0
"10000000", // rbspTrailingBits
want: PPS{
ID: 0,
SPSID: 0,
EntropyCodingMode: 1,
BottomFieldPicOrderInFramePresent: false,
NumSliceGroupsMinus1: 0,
NumRefIdxL0DefaultActiveMinus1: 0,
NumRefIdxL1DefaultActiveMinus1: 0,
WeightedPred: true,
WeightedBipred: 0,
PicInitQpMinus26: 0,
PicInitQsMinus26: 0,
ChromaQpIndexOffset: 0,
DeblockingFilterControlPresent: true,
ConstrainedIntraPred: false,
RedundantPicCntPresent: false,
},
},
{
in: "1" + // ue(v) pic_parameter_set_id = 0
"1" + // ue(v) seq_parameter_set_id = 0
"1" + // u(1) entropy_coding_mode_flag = 1
"1" + // u(1) bottom_field_pic_order_in_frame_present_flag = 1
"010" + // ue(v) num_slice_groups_minus1 = 1
"1" + // ue(v) slice_group_map_type = 0
"1" + // ue(v) run_length_minus1[0] = 0
"1" + // ue(v) run_length_minus1[1] = 0
"1" + // ue(v) num_ref_idx_L0_active_minus1 = 0
"1" + // ue(v) num_ref_idx_L1_active_minus1 = 0
"1" + // u(1) weighted_pred_flag = 0
"00" + // u(2) weighted_bipred_idc = 0
"011" + // se(v) pic_init_qp_minus26 = -1
"010" + // se(v) pic_init_qs_minus26 = 1
"00100" + // se(v) chroma_qp_index_offset = 2
"0" + // u(1) deblocking_filter_control_present_flag =0
"0" + // u(1) constrained_intra_pred_flag=0
"0" + // u(1) redundant_pic_cnt_present_flag=0
"0" + // u(1) transform_8x8_mode_flag=0
"0" + // u(1) pic_scaling_matrix_present_flag=0
"00100" + // se(v) second_chroma_qp_index_offset=2
"10000", // stop bit and trailing bits
want: PPS{
ID: 0,
SPSID: 0,
EntropyCodingMode: 1,
BottomFieldPicOrderInFramePresent: true,
NumSliceGroupsMinus1: 1,
RunLengthMinus1: []int{0, 0},
NumRefIdxL0DefaultActiveMinus1: 0,
NumRefIdxL1DefaultActiveMinus1: 0,
WeightedPred: true,
WeightedBipred: 0,
PicInitQpMinus26: -1,
PicInitQsMinus26: 1,
ChromaQpIndexOffset: 2,
DeblockingFilterControlPresent: false,
ConstrainedIntraPred: false,
RedundantPicCntPresent: false,
Transform8x8Mode: 0,
PicScalingMatrixPresent: false,
SecondChromaQpIndexOffset: 2,
},
},
}
for i, test := range tests {
bin, err := binToSlice(test.in)
if err != nil {
t.Fatalf("error: %v converting binary string to slice for test: %d", err, i)
}
pps, err := NewPPS(bits.NewBitReader(bytes.NewReader(bin)), test.chromaFormat)
if err != nil {
t.Fatalf("did not expect error: %v for test: %d", err, i)
}
if !reflect.DeepEqual(test.want, *pps) {
t.Errorf("did not get expected result for test: %d.\nGot: %+v\nWant: %+v\n", i, *pps, test.want)
}
}
}

View File

@ -0,0 +1,100 @@
package h264dec
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
}

View File

@ -0,0 +1,86 @@
package h264dec
import (
"fmt"
"bitbucket.org/ausocean/av/codec/h264/h264dec/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
}

270
codec/h264/h264dec/read.go Normal file
View File

@ -0,0 +1,270 @@
package h264dec
import (
"fmt"
"io"
"os"
"bitbucket.org/ausocean/av/codec/h264/h264dec/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(nil, videoStream.SPS.ChromaFormat)
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)
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
}
func moreRBSPData(br *bits.BitReader) bool {
// If we get an error then we must at end of NAL unit or end of stream, so
// return false.
b, err := br.PeekBits(1)
if err != nil {
return false
}
// If b is not 1, then we don't have a stop bit and therefore there is more
// data so return true.
if b == 0 {
return true
}
// If we have a stop bit and trailing zeros then we're okay, otherwise return
// now, we haven't found the end.
b, err = br.PeekBits(8 - br.Off())
if err != nil {
return false
}
rem := 0x01 << uint(7-br.Off())
if int(b) != rem {
return true
}
// If we try to read another bit but get EOF then we must be at the end of the
// NAL or stream.
_, err = br.PeekBits(9 - br.Off())
if err != nil {
return false
}
// Do we have some trailing 0 bits, and then a 24-bit start code ? If so, it
// there must not be any more RBSP data left.
// If we get an error from the Peek, then there must not be another NAL, and
// there must be some more RBSP, because trailing bits do not extend past the
// byte in which the stop bit is found.
b, err = br.PeekBits(8 - br.Off() + 24)
if err != nil {
return true
}
rem = (0x01 << uint((7-br.Off())+24)) | 0x01
if int(b) == rem {
return false
}
// Similar check to above, but this time checking for 32-bit start code.
b, err = br.PeekBits(8 - br.Off() + 32)
if err != nil {
return true
}
rem = (0x01 << uint((7-br.Off())+32)) | 0x01
if int(b) == rem {
return false
}
return true
}
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,59 @@
/*
DESCRIPTION
read_test.go provides testing for utilities in read.go.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>, The Australian Ocean Laboratory (AusOcean)
*/
package h264dec
import (
"bytes"
"testing"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
)
func TestMoreRBSPData(t *testing.T) {
tests := []struct {
in string
want bool
}{
{
in: "00000100",
want: true,
},
{
in: "10000100",
want: true,
},
{
in: "10000000",
want: false,
},
{
in: "10000000 00000000 00000000 00000001",
want: false,
},
{
in: "10000000 00000000 00000000 00000000 00000001",
want: false,
},
{
in: "10000000 00000000",
want: true,
},
}
for i, test := range tests {
b, err := binToSlice(test.in)
if err != nil {
t.Fatalf("unexpected binToSlice error: %v for test: %d", err, i)
}
got := moreRBSPData(bits.NewBitReader(bytes.NewReader(b)))
if got != test.want {
t.Errorf("unexpected result for test: %d\nGot: %v\nWant: %v\n", i, got, test.want)
}
}
}

View File

@ -0,0 +1,70 @@
package h264dec
import (
// "github.com/nareix/joy4/av"
// "github.com/nareix/joy4/codec/h264parser"
// "github.com/nareix/joy4/format/ts"
"io"
"log"
"net"
"os"
"os/signal"
"bitbucket.org/ausocean/av/codec/h264/h264dec/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()
}

1434
codec/h264/h264dec/slice.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
package h264dec
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/h264dec/sps.go Normal file
View File

@ -0,0 +1,690 @@
package h264dec
import (
"bytes"
"fmt"
"strings"
"bitbucket.org/ausocean/av/codec/h264/h264dec/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(br *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(br)
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(br)
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(br)
if err != nil {
return errors.Wrap(err, "could not parse BitRateValueMinus1")
}
sps.BitRateValueMinus1 = append(sps.BitRateValueMinus1, ue)
ue, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse ID")
}
sps.ChromaFormat, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse BitDepthLumaMinus8")
}
sps.BitDepthChromaMinus8, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse Log2MaxFrameNumMinus4")
}
sps.PicOrderCountType, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse PicOrderCountType")
}
if sps.PicOrderCountType == 0 {
sps.Log2MaxPicOrderCntLSBMin4, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse OffsetForNonRefPic")
}
sps.OffsetForTopToBottomField, err = readSe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse OffsetForTopToBottomField")
}
sps.NumRefFramesInPicOrderCntCycle, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse NumRefFramesInPicOrderCntCycle")
}
for i := 0; i < sps.NumRefFramesInPicOrderCntCycle; i++ {
se, err := readSe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse OffsetForRefFrameList")
}
sps.OffsetForRefFrameList = append(
sps.OffsetForRefFrameList,
se)
}
}
sps.MaxNumRefFrames, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse PicWidthInMbsMinus1")
}
sps.PicHeightInMapUnitsMinus1, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse FrameCropLeftOffset")
}
sps.FrameCropRightOffset, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse FrameCropRightOffset")
}
sps.FrameCropTopOffset, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse FrameCropTopOffset")
}
sps.FrameCropBottomOffset, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse ChromaSampleLocTypeTopField")
}
sps.ChromaSampleLocTypeBottomField, err = readUe(br)
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(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse MaxBytesPerPicDenom")
}
sps.MaxBitsPerMbDenom, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse MaxBitsPerMbDenom")
}
sps.Log2MaxMvLengthHorizontal, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse Log2MaxMvLengthHorizontal")
}
sps.Log2MaxMvLengthVertical, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse Log2MaxMvLengthVertical")
}
sps.MaxNumReorderFrames, err = readUe(br)
if err != nil {
return nil, errors.Wrap(err, "could not parse MaxNumReorderFrames")
}
sps.MaxDecFrameBuffering, err = readUe(br)
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 h264dec
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},
}

View File

@ -34,10 +34,10 @@ import (
"bitbucket.org/ausocean/av/container/mts/psi"
)
// Some common manifestations of PSI
// Some common manifestations of PSI.
var (
// standardPat is a minimal PAT.
standardPat = psi.PSI{
// StandardPAT is a minimal PAT.
StandardPAT = psi.PSI{
Pf: 0x00,
Tid: 0x00,
Ssi: true,
@ -55,6 +55,21 @@ var (
},
},
}
// Base PMT is a minimal PMT without specific data.
BasePMT = psi.PSI{
Pf: 0x00,
Tid: 0x02,
Ssi: true,
Sl: 0x12,
Tss: &psi.TSS{
Tide: 0x01,
V: 0,
Cni: true,
Sn: 0,
Lsn: 0,
},
}
)
const (
@ -69,7 +84,7 @@ const (
var Meta *meta.Data
var (
patTable = standardPat.Bytes()
patTable = StandardPAT.Bytes()
pmtTable []byte
)
@ -97,6 +112,9 @@ const (
// PTSFrequency is the presentation timestamp frequency in Hz.
PTSFrequency = 90000
// MaxPTS is the largest PTS value (i.e., for a 33-bit unsigned integer).
MaxPTS = (1 << 33) - 1
)
// Encoder encapsulates properties of an MPEG-TS generator.
@ -138,19 +156,8 @@ func NewEncoder(dst io.WriteCloser, rate float64, mediaType int) *Encoder {
sid = H264ID
}
// standardPmt is a minimal PMT, without descriptors for metadata.
pmtTable = (&psi.PSI{
Pf: 0x00,
Tid: 0x02,
Ssi: true,
Sl: 0x12,
Tss: &psi.TSS{
Tide: 0x01,
V: 0,
Cni: true,
Sn: 0,
Lsn: 0,
Sd: &psi.PMT{
pmt := BasePMT
pmt.Tss.Sd = &psi.PMT{
Pcrpid: 0x0100,
Pil: 0,
Essd: &psi.ESSD{
@ -158,9 +165,8 @@ func NewEncoder(dst io.WriteCloser, rate float64, mediaType int) *Encoder {
Epid: 0x0100,
Esil: 0x00,
},
},
},
}).Bytes()
}
pmtTable = pmt.Bytes()
return &Encoder{
dst: dst,

View File

@ -29,12 +29,14 @@ import (
"bytes"
"io"
"io/ioutil"
"reflect"
"testing"
"github.com/Comcast/gots/packet"
"github.com/Comcast/gots/pes"
"bitbucket.org/ausocean/av/container/mts/meta"
"bitbucket.org/ausocean/av/container/mts/psi"
)
type nopCloser struct{ io.Writer }
@ -250,3 +252,87 @@ func TestEncodePcm(t *testing.T) {
t.Error("data decoded from mts did not match input data")
}
}
const fps = 25
// TestMetaEncode1 checks that we can externally add a single metadata entry to
// the mts global Meta meta.Data struct and then successfully have the mts encoder
// write this to psi.
func TestMetaEncode1(t *testing.T) {
Meta = meta.New()
var buf bytes.Buffer
e := NewEncoder(nopCloser{&buf}, fps, EncodeH264)
Meta.Add("ts", "12345678")
if err := e.writePSI(); err != nil {
t.Errorf("unexpected error: %v\n", err.Error())
}
out := buf.Bytes()
got := out[PacketSize+4:]
want := []byte{
0x00, 0x02, 0xb0, 0x23, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x11,
psi.MetadataTag, // Descriptor tag
0x0f, // Length of bytes to follow
0x00, 0x10, 0x00, 0x0b, 't', 's', '=', '1', '2', '3', '4', '5', '6', '7', '8', // timestamp
0x1b, 0xe1, 0x00, 0xf0, 0x00,
}
want = psi.AddCrc(want)
want = psi.AddPadding(want)
if !bytes.Equal(got, want) {
t.Errorf("unexpected output. \n Got : %v\n, Want: %v\n", got, want)
}
}
// TestMetaEncode2 checks that we can externally add two metadata entries to the
// Meta meta.Data global and then have the mts encoder successfully encode this
// into psi.
func TestMetaEncode2(t *testing.T) {
Meta = meta.New()
var buf bytes.Buffer
e := NewEncoder(nopCloser{&buf}, fps, EncodeH264)
Meta.Add("ts", "12345678")
Meta.Add("loc", "1234,4321,1234")
if err := e.writePSI(); err != nil {
t.Errorf("did not expect error: %v from writePSI", err.Error())
}
out := buf.Bytes()
got := out[PacketSize+4:]
want := []byte{
0x00, 0x02, 0xb0, 0x36, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x24,
psi.MetadataTag, // Descriptor tag
0x22, // Length of bytes to follow
0x00, 0x10, 0x00, 0x1e, 't', 's', '=', '1', '2', '3', '4', '5', '6', '7', '8', '\t', // timestamp
'l', 'o', 'c', '=', '1', '2', '3', '4', ',', '4', '3', '2', '1', ',', '1', '2', '3', '4', // location
0x1b, 0xe1, 0x00, 0xf0, 0x00,
}
want = psi.AddCrc(want)
want = psi.AddPadding(want)
if !bytes.Equal(got, want) {
t.Errorf("did not get expected results.\ngot: %v\nwant: %v\n", got, want)
}
}
// TestExtractMeta checks that ExtractMeta can correclty get a map of metadata
// from the first PMT in a clip of MPEG-TS.
func TestExtractMeta(t *testing.T) {
Meta = meta.New()
var buf bytes.Buffer
e := NewEncoder(nopCloser{&buf}, fps, EncodeH264)
Meta.Add("ts", "12345678")
Meta.Add("loc", "1234,4321,1234")
if err := e.writePSI(); err != nil {
t.Errorf("did not expect error: %v", err.Error())
}
out := buf.Bytes()
got, err := ExtractMeta(out)
if err != nil {
t.Errorf("did not expect error: %v", err.Error())
}
want := map[string]string{
"ts": "12345678",
"loc": "1234,4321,1234",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("did not get expected result.\ngot: %v\nwant: %v\n", got, want)
}
}

View File

@ -51,7 +51,7 @@ const (
var (
errKeyAbsent = errors.New("Key does not exist in map")
errInvalidMeta = errors.New("Invalid metadata given")
errUnexpectedMetaFormat = errors.New("Unexpected meta format")
ErrUnexpectedMetaFormat = errors.New("Unexpected meta format")
)
// Metadata provides functionality for the storage and encoding of metadata
@ -209,13 +209,41 @@ func GetAll(d []byte) ([][2]string, error) {
for i, entry := range entries {
kv := strings.Split(entry, "=")
if len(kv) != 2 {
return nil, errUnexpectedMetaFormat
return nil, ErrUnexpectedMetaFormat
}
copy(all[i][:], kv)
}
return all, nil
}
// GetAllAsMap returns a map containing keys and values from a slice d containing
// metadata.
func GetAllAsMap(d []byte) (map[string]string, error) {
err := checkMeta(d)
if err != nil {
return nil, err
}
// Skip the header, which is our data length and version.
d = d[headSize:]
// Each metadata entry (key and value) is seperated by a tab, so split at tabs
// to get individual entries.
entries := strings.Split(string(d), "\t")
// Go through entries and add to all map.
all := make(map[string]string)
for _, entry := range entries {
// Keys and values are seperated by '=', so split and check that len(kv)=2.
kv := strings.Split(entry, "=")
if len(kv) != 2 {
return nil, ErrUnexpectedMetaFormat
}
all[kv[0]] = kv[1]
}
return all, nil
}
// checkHeader checks that a valid metadata header exists in the given data.
func checkMeta(d []byte) error {
if len(d) == 0 || d[0] != 0 || binary.BigEndian.Uint16(d[2:headSize]) != uint16(len(d[headSize:])) {

View File

@ -189,6 +189,23 @@ func TestGetAll(t *testing.T) {
}
}
// TestGetAllAsMap checks that GetAllAsMap will correctly return a map of meta
// keys and values from a slice of meta.
func TestGetAllAsMap(t *testing.T) {
tstMeta := append([]byte{0x00, 0x10, 0x00, 0x12}, "loc=a,b,c\tts=12345"...)
want := map[string]string{
"loc": "a,b,c",
"ts": "12345",
}
got, err := GetAllAsMap(tstMeta)
if err != nil {
t.Errorf("Unexpected error: %v\n", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Did not get expected out. \nGot : %v, \nWant: %v\n", got, want)
}
}
// TestKeys checks that we can successfully get keys from some metadata using
// the meta.Keys method.
func TestKeys(t *testing.T) {

View File

@ -1,100 +0,0 @@
/*
NAME
metaEncode_test.go
DESCRIPTION
See Readme.md
AUTHOR
Saxon Nelson-Milton <saxon@ausocean.org>
LICENSE
metaEncode_test.go is Copyright (C) 2017-2019 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
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package mts
import (
"bytes"
"testing"
"bitbucket.org/ausocean/av/container/mts/meta"
"bitbucket.org/ausocean/av/container/mts/psi"
)
const (
errNotExpectedOut = "Unexpected output. \n Got : %v\n, Want: %v\n"
errUnexpectedErr = "Unexpected error: %v\n"
)
const fps = 25
// TestMetaEncode1 checks that we can externally add a single metadata entry to
// the mts global Meta meta.Data struct and then successfully have the mts encoder
// write this to psi.
func TestMetaEncode1(t *testing.T) {
Meta = meta.New()
var buf bytes.Buffer
e := NewEncoder(nopCloser{&buf}, fps, EncodeH264)
Meta.Add("ts", "12345678")
if err := e.writePSI(); err != nil {
t.Errorf(errUnexpectedErr, err.Error())
}
out := buf.Bytes()
got := out[PacketSize+4:]
want := []byte{
0x00, 0x02, 0xb0, 0x23, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x11,
psi.MetadataTag, // Descriptor tag
0x0f, // Length of bytes to follow
0x00, 0x10, 0x00, 0x0b, 't', 's', '=', '1', '2', '3', '4', '5', '6', '7', '8', // timestamp
0x1b, 0xe1, 0x00, 0xf0, 0x00,
}
want = psi.AddCrc(want)
want = psi.AddPadding(want)
if !bytes.Equal(got, want) {
t.Errorf(errNotExpectedOut, got, want)
}
}
// TestMetaEncode2 checks that we can externally add two metadata entries to the
// Meta meta.Data global and then have the mts encoder successfully encode this
// into psi.
func TestMetaEncode2(t *testing.T) {
Meta = meta.New()
var buf bytes.Buffer
e := NewEncoder(nopCloser{&buf}, fps, EncodeH264)
Meta.Add("ts", "12345678")
Meta.Add("loc", "1234,4321,1234")
if err := e.writePSI(); err != nil {
t.Errorf(errUnexpectedErr, err.Error())
}
out := buf.Bytes()
got := out[PacketSize+4:]
want := []byte{
0x00, 0x02, 0xb0, 0x36, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x24,
psi.MetadataTag, // Descriptor tag
0x22, // Length of bytes to follow
0x00, 0x10, 0x00, 0x1e, 't', 's', '=', '1', '2', '3', '4', '5', '6', '7', '8', '\t', // timestamp
'l', 'o', 'c', '=', '1', '2', '3', '4', ',', '4', '3', '2', '1', ',', '1', '2', '3', '4', // location
0x1b, 0xe1, 0x00, 0xf0, 0x00,
}
want = psi.AddCrc(want)
want = psi.AddPadding(want)
if !bytes.Equal(got, want) {
t.Errorf(errNotExpectedOut, got, want)
}
}

View File

@ -30,11 +30,13 @@ LICENSE
package mts
import (
"errors"
"fmt"
"github.com/Comcast/gots/packet"
"github.com/Comcast/gots/pes"
"github.com/pkg/errors"
"bitbucket.org/ausocean/av/container/mts/meta"
"bitbucket.org/ausocean/av/container/mts/psi"
)
const PacketSize = 188
@ -174,11 +176,18 @@ func FindPat(d []byte) ([]byte, int, error) {
return FindPid(d, PatPid)
}
// Errors used by FindPid.
var (
errInvalidLen = errors.New("MPEG-TS data not of valid length")
errCouldNotFind = errors.New("could not find packet with given PID")
errNotConsecutive = errors.New("could not find consecutive PIDs")
)
// FindPid will take a clip of MPEG-TS and try to find a packet with given PID - if one
// is found, then it is returned along with its index, otherwise nil, -1 and an error is returned.
func FindPid(d []byte, pid uint16) (pkt []byte, i int, err error) {
if len(d) < PacketSize {
return nil, -1, errors.New("MPEG-TS data not of valid length")
return nil, -1, errInvalidLen
}
for i = 0; i < len(d); i += PacketSize {
p := (uint16(d[i+1]&0x1f) << 8) | uint16(d[i+2])
@ -187,7 +196,52 @@ func FindPid(d []byte, pid uint16) (pkt []byte, i int, err error) {
return
}
}
return nil, -1, fmt.Errorf("could not find packet with pid: %d", pid)
return nil, -1, errCouldNotFind
}
// LastPid will take a clip of MPEG-TS and try to find a packet
// with given PID searching in reverse from the end of the clip. If
// one is found, then it is returned along with its index, otherwise
// nil, -1 and an error is returned.
func LastPid(d []byte, pid uint16) (pkt []byte, i int, err error) {
if len(d) < PacketSize {
return nil, -1, errInvalidLen
}
for i = len(d) - PacketSize; i >= 0; i -= PacketSize {
p := (uint16(d[i+1]&0x1f) << 8) | uint16(d[i+2])
if p == pid {
pkt = d[i : i+PacketSize]
return
}
}
return nil, -1, errCouldNotFind
}
// IndexPid returns the position of one or more consecutive pids,
// along with optional metadata if present. Commonly used to find a
// PAT immediately followed by a PMT.
func IndexPid(d []byte, pids ...uint16) (idx int, m map[string]string, err error) {
idx = -1
for _, pid := range pids {
if len(d) < PacketSize {
return idx, m, errInvalidLen
}
pkt, i, err := FindPid(d, pid)
if err != nil {
return idx, m, errCouldNotFind
}
if pid == PmtPid {
m, _ = metaFromPMT(pkt)
}
if idx == -1 {
idx = i
} else if i != 0 {
return idx, m, errNotConsecutive
}
d = d[i+PacketSize:]
}
return
}
// FillPayload takes a channel and fills the packets Payload field until the
@ -319,46 +373,223 @@ func DiscontinuityIndicator(f bool) Option {
}
}
// Error used by GetPTSRange.
var errNoPTS = errors.New("could not find PTS")
// GetPTSRange retreives the first and last PTS of an MPEGTS clip.
// If there is only one PTS, it is included twice in the pts return value.
func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) {
// Find the first packet with PID pidType.
pkt, _, err := FindPid(clip, pid)
var _pts int64
// Get the first PTS for the given PID.
var i int
for {
if i >= len(clip) {
return pts, errNoPTS
}
pkt, _i, err := FindPid(clip[i:], pid)
if err != nil {
return [2]uint64{}, err
return pts, errors.Wrap(err, fmt.Sprintf("could not find packet of PID: %d", pid))
}
_pts, err = GetPTS(pkt)
if err == nil {
break
}
i += _i + PacketSize
}
// Get the payload of the packet, which will be the start of the PES packet.
var _pkt packet.Packet
copy(_pkt[:], pkt)
payload, err := packet.Payload(&_pkt)
if err != nil {
fmt.Printf("_pkt: %v\n", _pkt)
return [2]uint64{}, err
pts[0] = uint64(_pts)
pts[1] = pts[0] // Until we have find a second PTS.
// Get the last PTS searching in reverse from end of the clip.
first := i
i = len(clip)
for {
pkt, _i, err := LastPid(clip[:i], pid)
if err != nil || i <= first {
return pts, nil
}
_pts, err = GetPTS(pkt)
if err == nil {
break
}
i = _i
}
// Get the the first PTS from the PES header.
_pes, err := pes.NewPESHeader(payload)
if err != nil {
return [2]uint64{}, err
}
pts[0] = _pes.PTS()
pts[1] = uint64(_pts)
// Get the final PTS searching from end of clip for access unit start.
for i := len(clip) - PacketSize; i >= 0; i -= PacketSize {
copy(_pkt[:], clip[i:i+PacketSize])
if packet.PayloadUnitStartIndicator(&_pkt) && uint16(_pkt.PID()) == pid {
payload, err = packet.Payload(&_pkt)
if err != nil {
return [2]uint64{}, err
}
_pes, err = pes.NewPESHeader(payload)
if err != nil {
return [2]uint64{}, err
}
pts[1] = _pes.PTS()
return
}
var (
errNoPesPayload = errors.New("no PES payload")
errNoPesPTS = errors.New("no PES PTS")
errInvalidPesHeader = errors.New("invalid PES header")
errInvalidPesPayload = errors.New("invalid PES payload")
)
// GetPTS returns a PTS from a packet that has PES payload, or an error otherwise.
func GetPTS(pkt []byte) (pts int64, err error) {
// Check the Payload Unit Start Indicator.
if pkt[1]&0x040 == 0 {
err = errNoPesPayload
return
}
// Compute start of PES payload and check its length.
start := HeadSize
if pkt[3]&0x20 != 0 {
// Adaptation field is present, so adjust start of payload accordingly.
start += 1 + int(pkt[4])
}
pes := pkt[start:]
if len(pes) < 14 {
err = errInvalidPesHeader
return
}
return [2]uint64{}, errors.New("could only find one access unit in mpegts clip")
// Check the PTS DTS indicator.
if pes[7]&0xc0 == 0 {
err = errNoPesPTS
return
}
pts = extractPTS(pes[9:14])
return
}
// extractTime extracts a PTS from the given data.
func extractPTS(d []byte) int64 {
return (int64((d[0]>>1)&0x07) << 30) | (int64(d[1]) << 22) | (int64((d[2]>>1)&0x7f) << 15) | (int64(d[3]) << 7) | int64((d[4]>>1)&0x7f)
}
var errNoMeta = errors.New("PMT does not contain meta")
// ExtractMeta returns a map of metadata from the first PMT's metaData
// descriptor, that is found in the MPEG-TS clip d. d must contain a series of
// complete MPEG-TS packets.
func ExtractMeta(d []byte) (map[string]string, error) {
pmt, _, err := FindPid(d, PmtPid)
if err != nil {
return nil, err
}
return metaFromPMT(pmt)
}
// metaFromPMT returns metadata, if any, from a PMT.
func metaFromPMT(d []byte) (m map[string]string, err error) {
// Get as PSI type, skipping the MTS header.
pmt := psi.PSIBytes(d[HeadSize:])
// Get the metadata descriptor.
_, desc := pmt.HasDescriptor(psi.MetadataTag)
if desc == nil {
return m, errNoMeta
}
// Get the metadata as a map, skipping the descriptor head.
return meta.GetAllAsMap(desc[2:])
}
// TrimToMetaRange trims a slice of MPEG-TS to a segment between two points of
// meta data described by key, from and to.
func TrimToMetaRange(d []byte, key, from, to string) ([]byte, error) {
if len(d)%PacketSize != 0 {
return nil, errors.New("MTS clip is not of valid size")
}
if from == to {
return nil, errors.New("'from' and 'to' cannot be identical")
}
var (
start = -1 // Index of the start of the segment in d.
end = -1 // Index of the end of segment in d.
off int // Index of remaining slice of d to check after each PMT found.
)
for {
// Find the next PMT.
pmt, idx, err := FindPid(d[off:], PmtPid)
if err != nil {
switch -1 {
case start:
return nil, errMetaLowerBound
case end:
return nil, errMetaUpperBound
default:
panic("should not have got error from FindPid")
}
}
off += idx + PacketSize
meta, err := ExtractMeta(pmt)
switch err {
case nil: // do nothing
case errNoMeta:
continue
default:
return nil, err
}
if start == -1 {
if meta[key] == from {
start = off - PacketSize
}
} else if meta[key] == to {
end = off
return d[start:end], nil
}
}
}
// SegmentForMeta returns segments of MTS slice d that correspond to a value of
// meta for key and val. Therefore, any sequence of packets corresponding to
// key and val will be appended to the returned [][]byte.
func SegmentForMeta(d []byte, key, val string) ([][]byte, error) {
var (
pkt packet.Packet // We copy data to this so that we can use comcast gots stuff.
segmenting bool // If true we are currently in a segment corresponsing to given meta.
res [][]byte // The resultant [][]byte holding the segments.
start int // The start index of the current segment.
)
// Go through packets.
for i := 0; i < len(d); i += PacketSize {
copy(pkt[:], d[i:i+PacketSize])
if pkt.PID() == PmtPid {
_meta, err := ExtractMeta(pkt[:])
switch err {
// If there's no meta or a problem with meta, we consider this the end
// of the segment.
case errNoMeta, meta.ErrUnexpectedMetaFormat:
if segmenting {
res = append(res, d[start:i])
segmenting = false
}
continue
case nil: // do nothing.
default:
return nil, err
}
// If we've got the meta of interest in the PMT and we're not segmenting
// then start segmenting. If we don't have the meta of interest in the PMT
// and we are segmenting then we want to stop and append the segment to result.
if _meta[key] == val && !segmenting {
start = i
segmenting = true
} else if _meta[key] != val && segmenting {
res = append(res, d[start:i])
segmenting = false
}
}
}
// We've reached the end of the entire MTS clip so if we're segmenting we need
// to append current segment to res.
if segmenting {
res = append(res, d[start:len(d)])
}
return res, nil
}

View File

@ -30,17 +30,20 @@ package mts
import (
"bytes"
"math/rand"
"reflect"
"strconv"
"testing"
"time"
"bitbucket.org/ausocean/av/container/mts/meta"
"bitbucket.org/ausocean/av/container/mts/pes"
"bitbucket.org/ausocean/av/container/mts/psi"
"github.com/Comcast/gots/packet"
)
// TestGetPTSRange checks that GetPTSRange can correctly get the first and last
// PTS in an MPEGTS clip.
func TestGetPTSRange(t *testing.T) {
// PTS in an MPEGTS clip for a general case.
func TestGetPTSRange1(t *testing.T) {
const (
numOfFrames = 20
maxFrameSize = 1000
@ -157,6 +160,86 @@ func writeFrame(b *bytes.Buffer, frame []byte, pts uint64) error {
return nil
}
// TestGetPTSRange2 checks that GetPTSRange behaves correctly with cases where
// the first instance of a PID is not a payload start, and also where there
// are no payload starts.
func TestGetPTSRange2(t *testing.T) {
const (
nPackets = 8 // The number of MTS packets we will generate.
wantPID = 1 // The PID we want.
)
tests := []struct {
pusi []bool // The value of PUSI for each packet.
pid []uint16 // The PIDs for each packet.
pts []uint64 // The PTS for each packet.
want [2]uint64 // The wanted PTS from GetPTSRange.
err error // The error we expect from GetPTSRange.
}{
{
[]bool{false, false, false, true, false, false, true, false},
[]uint16{0, 0, 1, 1, 1, 1, 1, 1},
[]uint64{0, 0, 0, 1, 0, 0, 2, 0},
[2]uint64{1, 2},
nil,
},
{
[]bool{false, false, false, true, false, false, false, false},
[]uint16{0, 0, 1, 1, 1, 1, 1, 1},
[]uint64{0, 0, 0, 1, 0, 0, 0, 0},
[2]uint64{1, 1},
nil,
},
{
[]bool{false, false, false, false, false, false, false, false},
[]uint16{0, 0, 1, 1, 1, 1, 1, 1},
[]uint64{0, 0, 0, 0, 0, 0, 0, 0},
[2]uint64{0, 0},
errNoPTS,
},
}
var clip bytes.Buffer
for i, test := range tests {
// Generate MTS packets for this test.
clip.Reset()
for j := 0; j < nPackets; j++ {
pesPkt := pes.Packet{
StreamID: H264ID,
PDI: hasPTS,
PTS: test.pts[j],
Data: []byte{},
HeaderLength: 5,
}
buf := pesPkt.Bytes(nil)
pkt := Packet{
PUSI: test.pusi[j],
PID: test.pid[j],
RAI: true,
CC: 0,
AFC: hasAdaptationField | hasPayload,
PCRF: true,
}
pkt.FillPayload(buf)
_, err := clip.Write(pkt.Bytes(nil))
if err != nil {
t.Fatalf("did not expect clip write error: %v", err)
}
}
pts, err := GetPTSRange(clip.Bytes(), wantPID)
if err != test.err {
t.Errorf("did not get expected error for test: %v\nGot: %v\nWant: %v\n", i, err, test.err)
}
if pts != test.want {
t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, pts, test.want)
}
}
}
// TestBytes checks that Packet.Bytes() correctly produces a []byte
// representation of a Packet.
func TestBytes(t *testing.T) {
@ -264,3 +347,186 @@ func TestFindPid(t *testing.T) {
t.Errorf("index of found packet is not correct.\nGot: %v, want: %v\n", _got, targetPacketNum)
}
}
// TestTrimToMetaRange checks that TrimToMetaRange can correctly return a segment
// of MPEG-TS corresponding to a meta interval in a slice of MPEG-TS.
func TestTrimToMetaRange(t *testing.T) {
Meta = meta.New()
const (
nPSI = 10
key = "n"
)
var clip bytes.Buffer
for i := 0; i < nPSI; i++ {
Meta.Add(key, strconv.Itoa((i*2)+1))
err := writePSIWithMeta(&clip)
if err != nil {
t.Fatalf("did not expect to get error writing PSI, error: %v", err)
}
}
tests := []struct {
from string
to string
expect []byte
err error
}{
{
from: "3",
to: "9",
expect: clip.Bytes()[3*PacketSize : 10*PacketSize],
err: nil,
},
{
from: "30",
to: "8",
expect: nil,
err: errMetaLowerBound,
},
{
from: "3",
to: "30",
expect: nil,
err: errMetaUpperBound,
},
}
// Run tests.
for i, test := range tests {
got, err := TrimToMetaRange(clip.Bytes(), key, test.from, test.to)
// First check the error.
if err != nil && err != test.err {
t.Errorf("unexpected error: %v for test: %v", err, i)
continue
} else if err != test.err {
t.Errorf("expected to get error: %v for test: %v", test.err, i)
continue
}
// Now check data.
if test.err == nil && !bytes.Equal(test.expect, got) {
t.Errorf("did not get expected data for test: %v\n Got: %v\n, Want: %v\n", i, got, test.expect)
}
}
}
// TestSegmentForMeta checks that SegmentForMeta can correctly segment some MTS
// data based on a given meta key and value.
func TestSegmentForMeta(t *testing.T) {
Meta = meta.New()
const (
nPSI = 10 // The number of PSI pairs to write.
key = "n" // The meta key we will work with.
val = "*" // This is the meta value we will look for.
)
tests := []struct {
metaVals [nPSI]string // This represents the meta value for meta pairs (PAT and PMT)
expectIdxs []rng // This gives the expect index ranges for the segments.
}{
{
metaVals: [nPSI]string{"1", "2", val, val, val, "3", val, val, "4", "4"},
expectIdxs: []rng{
scale(2, 5),
scale(6, 8),
},
},
{
metaVals: [nPSI]string{"1", "2", val, val, val, "", "3", val, val, "4"},
expectIdxs: []rng{
scale(2, 5),
scale(7, 9),
},
},
{
metaVals: [nPSI]string{"1", "2", val, val, val, "", "3", val, val, val},
expectIdxs: []rng{
scale(2, 5),
{((7 * 2) + 1) * PacketSize, (nPSI * 2) * PacketSize},
},
},
{
metaVals: [nPSI]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"},
expectIdxs: nil,
},
}
var clip bytes.Buffer
for testn, test := range tests {
// We want a clean buffer for each new test, so reset.
clip.Reset()
// Add meta and write PSI to clip.
for i := 0; i < nPSI; i++ {
if test.metaVals[i] != "" {
Meta.Add(key, test.metaVals[i])
} else {
Meta.Delete(key)
}
err := writePSIWithMeta(&clip)
if err != nil {
t.Fatalf("did not expect to get error writing PSI, error: %v", err)
}
}
// Now we get the expected segments using the index ranges from the test.
var want [][]byte
for _, idxs := range test.expectIdxs {
want = append(want, clip.Bytes()[idxs.start:idxs.end])
}
// Now use the function we're testing to get the segments.
got, err := SegmentForMeta(clip.Bytes(), key, val)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Check that segments are OK.
if !reflect.DeepEqual(want, got) {
t.Errorf("did not get expected result for test %v\nGot: %v\nWant: %v\n", testn, got, want)
}
// Now test IndexPid.
i, m, err := IndexPid(clip.Bytes(), PatPid, PmtPid)
if err != nil {
t.Fatalf("IndexPid failed with error: %v", err)
}
if i != 0 {
t.Fatalf("IndexPid unexpected index; got %d, expected 0", i)
}
if m["n"] != "1" {
t.Fatalf("IndexPid unexpected metadata; got %s, expected 1", m["n"])
}
}
// Finally, test IndexPid error handling.
for _, d := range [][]byte{[]byte{}, make([]byte, PacketSize/2), make([]byte, PacketSize)} {
_, _, err := IndexPid(d, PatPid, PmtPid)
if err == nil {
t.Fatalf("IndexPid expected error")
}
}
}
// rng describes an index range and is used by TestSegmentForMeta.
type rng struct {
start int
end int
}
// scale takes a PSI index (i.e. first PSI is 0, next is 1) and modifies to be
// the index of the first byte of the PSI pair (PAT and PMT) in the byte stream.
// This assumes there are only PSI written consequitively, and is used by
// TestSegmentForMeta.
func scale(x, y int) rng {
return rng{
((x * 2) + 1) * PacketSize,
((y * 2) + 1) * PacketSize,
}
}

306
container/mts/payload.go Normal file
View File

@ -0,0 +1,306 @@
/*
NAME
payload.go
DESCRIPTION
payload.go provides functionality for extracting and manipulating the payload
data from MPEG-TS.
AUTHOR
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2019 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
along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
*/
package mts
import (
"errors"
"sort"
"github.com/Comcast/gots/packet"
"github.com/Comcast/gots/pes"
)
// Extract extracts the media, PTS, stream ID and meta for an MPEG-TS clip given
// by p, and returns as a Clip. The MPEG-TS must contain only complete packets.
// The resultant data is a copy of the original.
func Extract(p []byte) (*Clip, error) {
l := len(p)
// Check that clip is divisible by 188, i.e. contains a series of full MPEG-TS clips.
if l%PacketSize != 0 {
return nil, errors.New("MTS clip is not of valid size")
}
var (
frameStart int // Index used to indicate the start of current frame in backing slice.
clip = &Clip{} // The data that will be returned.
meta map[string]string // Holds the most recently extracted meta.
lenOfFrame int // Len of current frame.
dataLen int // Len of data from MPEG-TS packet.
curPTS uint64 // Holds the current PTS.
curStreamID uint8 // Holds current StreamID (shouldn't change)
firstPUSI = true // Indicates that we have not yet received a PUSI.
err error
)
// This will hold a copy of all the media in the MPEG-TS clip.
clip.backing = make([]byte, 0, l/PacketSize)
// Go through the MPEGT-TS packets.
var pkt packet.Packet
for i := 0; i < l; i += PacketSize {
// We will use comcast/gots Packet type, so copy in.
copy(pkt[:], p[i:i+PacketSize])
switch pkt.PID() {
case PatPid: // Do nothing.
case PmtPid:
meta, err = ExtractMeta(pkt[:])
if err != nil {
return nil, err
}
default: // Must be media.
// Get the MPEG-TS payload.
payload, err := pkt.Payload()
if err != nil {
return nil, err
}
// If PUSI is true then we know it's the start of a new frame, and we have
// a PES header in the MTS payload.
if pkt.PayloadUnitStartIndicator() {
_pes, err := pes.NewPESHeader(payload)
if err != nil {
return nil, err
}
// Extract the PTS and ID, then add a new frame to the clip.
curPTS = _pes.PTS()
curStreamID = _pes.StreamId()
clip.frames = append(clip.frames, Frame{
PTS: curPTS,
ID: curStreamID,
Meta: meta,
})
// Append the data to the underlying buffer and get appended length.
clip.backing = append(clip.backing, _pes.Data()...)
dataLen = len(_pes.Data())
// If we haven't hit the first PUSI, then we know we have a full frame
// and can add this data to the frame pertaining to the finish frame.
if !firstPUSI {
clip.frames[len(clip.frames)-2].Media = clip.backing[frameStart:lenOfFrame]
clip.frames[len(clip.frames)-2].idx = frameStart
frameStart = lenOfFrame
}
firstPUSI = false
} else {
// We're not at the start of the frame, so we don't have a PES header.
// We can append the MPEG-TS data directly to the underlying buf.
dataLen = len(payload)
clip.backing = append(clip.backing, payload...)
}
lenOfFrame += dataLen
}
}
// We're finished up with media frames, so give the final Frame it's data.
clip.frames[len(clip.frames)-1].Media = clip.backing[frameStart:lenOfFrame]
clip.frames[len(clip.frames)-1].idx = frameStart
return clip, nil
}
// Clip represents a clip of media, i.e. a sequence of media frames.
type Clip struct {
frames []Frame
backing []byte
}
// Frame describes a media frame that may be extracted from a PES packet.
type Frame struct {
Media []byte // Contains the media from the frame.
PTS uint64 // PTS from PES packet (this gives time relative from start of stream).
ID uint8 // StreamID from the PES packet, identifying media codec.
Meta map[string]string // Contains metadata from PMT relevant to this frame.
idx int // Index in the backing slice.
}
// Bytes returns the concatentated media bytes from each frame in the Clip c.
func (c *Clip) Bytes() []byte {
if c.backing == nil {
panic("the clip backing array cannot be nil")
}
return c.backing
}
// Errors used in TrimToPTSRange.
var (
errPTSLowerBound = errors.New("PTS 'from' cannot be found")
errPTSUpperBound = errors.New("PTS 'to' cannot be found")
errPTSRange = errors.New("PTS interval invalid")
)
// TrimToPTSRange returns the sub Clip in a PTS range defined by from and to.
// The first Frame in the new Clip will be the Frame for which from corresponds
// exactly with Frame.PTS, or the Frame in which from lies within. The final
// Frame in the Clip will be the previous of that for which to coincides with,
// or the Frame that to lies within.
func (c *Clip) TrimToPTSRange(from, to uint64) (*Clip, error) {
// First check that the interval makes sense.
if from >= to {
return nil, errPTSRange
}
// Use binary search to find 'from'.
n := len(c.frames) - 1
startFrameIdx := sort.Search(
n,
func(i int) bool {
if from < c.frames[i+1].PTS {
return true
}
return false
},
)
if startFrameIdx == n {
return nil, errPTSLowerBound
}
// Now get the start index for the backing slice from this Frame.
startBackingIdx := c.frames[startFrameIdx].idx
// Now use binary search again to find 'to'.
off := startFrameIdx + 1
n = n - (off)
endFrameIdx := sort.Search(
n,
func(i int) bool {
if to <= c.frames[i+off].PTS {
return true
}
return false
},
)
if endFrameIdx == n {
return nil, errPTSUpperBound
}
// Now get the end index for the backing slice from this Frame.
endBackingIdx := c.frames[endFrameIdx+off-1].idx
// Now return a new clip. NB: data is not copied.
return &Clip{
frames: c.frames[startFrameIdx : endFrameIdx+1],
backing: c.backing[startBackingIdx : endBackingIdx+len(c.frames[endFrameIdx+off].Media)],
}, nil
}
// Errors that maybe returned from TrimToMetaRange.
var (
errMetaRange = errors.New("invalid meta range")
errMetaLowerBound = errors.New("meta 'from' cannot be found")
errMetaUpperBound = errors.New("meta 'to' cannot be found")
)
// TrimToMetaRange returns a sub Clip with meta range described by from and to
// with key 'key'. The meta values must not be equivalent.
func (c *Clip) TrimToMetaRange(key, from, to string) (*Clip, error) {
// First check that the interval makes sense.
if from == to {
return nil, errMetaRange
}
var start, end int
// Try and find from.
for i := 0; i < len(c.frames); i++ {
f := c.frames[i]
startFrameIdx := i
if f.Meta[key] == from {
start = f.idx
// Now try and find to.
for ; i < len(c.frames); i++ {
f = c.frames[i]
if f.Meta[key] == to {
end = f.idx
endFrameIdx := i
return &Clip{
frames: c.frames[startFrameIdx : endFrameIdx+1],
backing: c.backing[start : end+len(f.Media)],
}, nil
}
}
return nil, errMetaUpperBound
}
}
return nil, errMetaLowerBound
}
// SegmentForMeta segments sequences of frames within c possesing meta described
// by key and val and are appended to a []Clip which is subsequently returned.
func (c *Clip) SegmentForMeta(key, val string) []Clip {
var (
segmenting bool // If true we are currently in a segment corresponsing to given meta.
res []Clip // The resultant [][]Clip holding the segments.
start int // The start index of the current segment.
)
// Go through frames of clip.
for i, frame := range c.frames {
// If there is no meta (meta = nil) and we are segmenting, then append the
// current segment to res.
if frame.Meta == nil {
if segmenting {
res = appendSegment(res, c, start, i)
segmenting = false
}
continue
}
// If we've got the meta of interest in current frame and we're not
// segmenting, set this i as start and set segmenting true. If we don't
// have the meta of interest and we are segmenting then we
// want to stop and append the segment to res.
if frame.Meta[key] == val && !segmenting {
start = i
segmenting = true
} else if frame.Meta[key] != val && segmenting {
res = appendSegment(res, c, start, i)
segmenting = false
}
}
// We've reached the end of the entire clip so if we're segmenting we need
// to append current segment to res.
if segmenting {
res = appendSegment(res, c, start, len(c.frames))
}
return res
}
// appendSegment is a helper function used by Clip.SegmentForMeta to append a
// clip to a []Clip.
func appendSegment(segs []Clip, c *Clip, start, end int) []Clip {
return append(segs, Clip{
frames: c.frames[start:end],
backing: c.backing[c.frames[start].idx : c.frames[end-1].idx+len(c.frames[end-1].Media)],
},
)
}

View File

@ -0,0 +1,502 @@
/*
NAME
payload_test.go
DESCRIPTION
payload_test.go provides testing to validate utilities found in payload.go.
AUTHOR
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2017 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
along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
*/
package mts
import (
"bytes"
"math/rand"
"reflect"
"strconv"
"testing"
"time"
"bitbucket.org/ausocean/av/container/mts/meta"
"bitbucket.org/ausocean/av/container/mts/psi"
)
// TestExtract checks that we can coorectly extract media, pts, id and meta from
// an MPEGTS stream using Extract.
func TestExtract(t *testing.T) {
Meta = meta.New()
const (
psiInterval = 5 // Write PSI at start and after every 5 frames.
numOfFrames = 30 // Total number of frames to write.
maxFrameSize = 1000 // Max frame size to randomly generate.
minFrameSize = 100 // Min frame size to randomly generate.
rate = 25 // Framerate (fps)
interval = float64(1) / rate // Time interval between frames.
ptsFreq = 90000 // Standard PTS frequency base.
)
frames := genFrames(numOfFrames, minFrameSize, maxFrameSize)
var (
clip bytes.Buffer // This will hold the MPEG-TS data.
want Clip // This is the Clip that we should get.
err error
)
// Now write frames.
var curTime float64
for i, frame := range frames {
// Check to see if it's time to write another lot of PSI.
if i%psiInterval == 0 && i != len(frames)-1 {
// We'll add the frame number as meta.
Meta.Add("frameNum", strconv.Itoa(i))
err = writePSIWithMeta(&clip)
if err != nil {
t.Fatalf("did not expect error writing psi: %v", err)
}
}
nextPTS := uint64(curTime * ptsFreq)
err = writeFrame(&clip, frame, uint64(nextPTS))
if err != nil {
t.Fatalf("did not expect error writing frame: %v", err)
}
curTime += interval
// Need the meta map for the new expected Frame.
metaMap, err := meta.GetAllAsMap(Meta.Encode())
if err != nil {
t.Fatalf("did not expect error getting meta map: %v", err)
}
// Create an equivalent Frame and append to our Clip want.
want.frames = append(want.frames, Frame{
Media: frame,
PTS: nextPTS,
ID: H264ID,
Meta: metaMap,
})
}
// Now use Extract to get frames from clip.
got, err := Extract(clip.Bytes())
if err != nil {
t.Fatalf("did not expect error using Extract. Err: %v", err)
}
// Check length of got and want.
if len(want.frames) != len(got.frames) {
t.Fatalf("did not get expected length for got.\nGot: %v\n, Want: %v\n", len(got.frames), len(want.frames))
}
// Check frames individually.
for i, frame := range want.frames {
// Check media data.
wantMedia := frame.Media
gotMedia := got.frames[i].Media
if !bytes.Equal(wantMedia, gotMedia) {
t.Fatalf("did not get expected data for frame: %v\nGot: %v\nWant: %v\n", i, gotMedia, wantMedia)
}
// Check stream ID.
wantID := frame.ID
gotID := got.frames[i].ID
if wantID != gotID {
t.Fatalf("did not get expected ID for frame: %v\nGot: %v\nWant: %v\n", i, gotID, wantID)
}
// Check meta.
wantMeta := frame.Meta
gotMeta := got.frames[i].Meta
if !reflect.DeepEqual(wantMeta, gotMeta) {
t.Fatalf("did not get expected meta for frame: %v\nGot: %v\nwant: %v\n", i, gotMeta, wantMeta)
}
}
}
// writePSIWithMeta writes PSI to b with updated metadata.
func writePSIWithMeta(b *bytes.Buffer) error {
// Write PAT.
pat := Packet{
PUSI: true,
PID: PatPid,
CC: 0,
AFC: HasPayload,
Payload: psi.AddPadding(patTable),
}
_, err := b.Write(pat.Bytes(nil))
if err != nil {
return err
}
// Update the meta in the pmt table.
pmtTable, err = updateMeta(pmtTable)
if err != nil {
return err
}
// Write PMT.
pmt := Packet{
PUSI: true,
PID: PmtPid,
CC: 0,
AFC: HasPayload,
Payload: psi.AddPadding(pmtTable),
}
_, err = b.Write(pmt.Bytes(nil))
if err != nil {
return err
}
return nil
}
// TestClipBytes checks that Clip.Bytes correctly returns the concatendated media
// data from the Clip's frames slice.
func TestClipBytes(t *testing.T) {
Meta = meta.New()
const (
psiInterval = 5 // Write PSI at start and after every 5 frames.
numOfFrames = 30 // Total number of frames to write.
maxFrameSize = 1000 // Max frame size to randomly generate.
minFrameSize = 100 // Min frame size to randomly generate.
rate = 25 // Framerate (fps)
interval = float64(1) / rate // Time interval between frames.
ptsFreq = 90000 // Standard PTS frequency base.
)
frames := genFrames(numOfFrames, minFrameSize, maxFrameSize)
var (
clip bytes.Buffer // This will hold the MPEG-TS data.
want []byte // This is the Clip that we should get.
err error
)
// Now write frames.
var curTime float64
for i, frame := range frames {
// Check to see if it's time to write another lot of PSI.
if i%psiInterval == 0 && i != len(frames)-1 {
// We'll add the frame number as meta.
Meta.Add("frameNum", strconv.Itoa(i))
err = writePSIWithMeta(&clip)
if err != nil {
t.Fatalf("did not expect error writing psi: %v", err)
}
}
nextPTS := uint64(curTime * ptsFreq)
err = writeFrame(&clip, frame, uint64(nextPTS))
if err != nil {
t.Fatalf("did not expect error writing frame: %v", err)
}
curTime += interval
// Append the frame straight to the expected pure media slice.
want = append(want, frame...)
}
// Now use Extract to get Clip and then use Bytes to get the slice of straight media.
gotClip, err := Extract(clip.Bytes())
if err != nil {
t.Fatalf("did not expect error using Extract. Err: %v", err)
}
got := gotClip.Bytes()
// Check length and equality of got and want.
if len(want) != len(got) {
t.Fatalf("did not get expected length for got.\nGot: %v\n, Want: %v\n", len(got), len(want))
}
if !bytes.Equal(want, got) {
t.Error("did not get expected result")
}
}
// genFrames is a helper function to generate a series of dummy media frames
// with randomized size. n is the number of frames to generate, min is the min
// size is min size of random frame and max is max size of random frames.
func genFrames(n, min, max int) [][]byte {
// Generate randomly sized data for each frame and fill.
rand.Seed(time.Now().UnixNano())
frames := make([][]byte, n)
for i := range frames {
frames[i] = make([]byte, rand.Intn(max-min)+min)
for j := 0; j < len(frames[i]); j++ {
frames[i][j] = byte(j)
}
}
return frames
}
// TestTrimToPTSRange checks that Clip.TrimToPTSRange will correctly return a
// sub Clip of the given PTS range.
func TestTrimToPTSRange(t *testing.T) {
const (
numOfTestFrames = 10
ptsInterval = 4
frameSize = 3
)
clip := &Clip{}
// Generate test frames.
for i := 0; i < numOfTestFrames; i++ {
clip.backing = append(clip.backing, []byte{byte(i), byte(i), byte(i)}...)
clip.frames = append(
clip.frames,
Frame{
Media: clip.backing[i*frameSize : (i+1)*frameSize],
PTS: uint64(i * ptsInterval),
idx: i * frameSize,
},
)
}
// We test each of these scenarios.
tests := []struct {
from uint64
to uint64
expect []byte
err error
}{
{
from: 6,
to: 15,
expect: []byte{
0x01, 0x01, 0x01,
0x02, 0x02, 0x02,
0x03, 0x03, 0x03,
},
err: nil,
},
{
from: 4,
to: 16,
expect: []byte{
0x01, 0x01, 0x01,
0x02, 0x02, 0x02,
0x03, 0x03, 0x03,
},
err: nil,
},
{
from: 10,
to: 5,
expect: nil,
err: errPTSRange,
},
{
from: 50,
to: 70,
expect: nil,
err: errPTSLowerBound,
},
{
from: 5,
to: 70,
expect: nil,
err: errPTSUpperBound,
},
}
// Run tests.
for i, test := range tests {
got, err := clip.TrimToPTSRange(test.from, test.to)
// First check the error.
if err != nil && err != test.err {
t.Errorf("unexpected error: %v for test: %v", err, i)
continue
} else if err != test.err {
t.Errorf("expected to get error: %v for test: %v", test.err, i)
continue
}
// Now check data.
if test.err == nil && !bytes.Equal(test.expect, got.Bytes()) {
t.Errorf("did not get expected data for test: %v\n Got: %v\n, Want: %v\n", i, got, test.expect)
}
}
}
// TestTrimToMetaRange checks that Clip.TrimToMetaRange correctly provides a
// sub Clip for a given meta range.
func TestClipTrimToMetaRange(t *testing.T) {
const (
numOfTestFrames = 10
ptsInterval = 4
frameSize = 3
key = "n"
)
clip := &Clip{}
// Generate test frames.
for i := 0; i < numOfTestFrames; i++ {
clip.backing = append(clip.backing, []byte{byte(i), byte(i), byte(i)}...)
clip.frames = append(
clip.frames,
Frame{
Media: clip.backing[i*frameSize : (i+1)*frameSize],
idx: i * frameSize,
Meta: map[string]string{
key: strconv.Itoa(i),
},
},
)
}
// We test each of these scenarios.
tests := []struct {
from string
to string
expect []byte
err error
}{
{
from: "1",
to: "3",
expect: []byte{
0x01, 0x01, 0x01,
0x02, 0x02, 0x02,
0x03, 0x03, 0x03,
},
err: nil,
},
{
from: "1",
to: "1",
expect: nil,
err: errMetaRange,
},
{
from: "20",
to: "1",
expect: nil,
err: errMetaLowerBound,
},
{
from: "1",
to: "20",
expect: nil,
err: errMetaUpperBound,
},
}
// Run tests.
for i, test := range tests {
got, err := clip.TrimToMetaRange(key, test.from, test.to)
// First check the error.
if err != nil && err != test.err {
t.Errorf("unexpected error: %v for test: %v", err, i)
continue
} else if err != test.err {
t.Errorf("expected to get error: %v for test: %v", test.err, i)
continue
}
// Now check data.
if test.err == nil && !bytes.Equal(test.expect, got.Bytes()) {
t.Errorf("did not get expected data for test: %v\n Got: %v\n, Want: %v\n", i, got, test.expect)
}
}
}
// TestClipSegmentForMeta checks that Clip.SegmentForMeta correctly returns
// segments from a clip with consistent meta defined by a key and value.
func TestClipSegmentForMeta(t *testing.T) {
const (
nFrames = 10 // The number of test frames we want to create.
fSize = 3 // The size of the frame media.
key = "n" // Meta key we will use.
val = "*" // The meta val of interest.
)
tests := []struct {
metaVals []string // These will be the meta vals each frame has.
fIndices []rng // These are the indices of the segments of interest.
}{
{
metaVals: []string{"1", "2", "*", "*", "*", "3", "*", "*", "4", "5"},
fIndices: []rng{{2, 5}, {6, 8}},
},
{
metaVals: []string{"1", "2", "*", "*", "*", "", "*", "*", "4", "5"},
fIndices: []rng{{2, 5}, {6, 8}},
},
{
metaVals: []string{"1", "2", "*", "*", "*", "3", "4", "5", "*", "*"},
fIndices: []rng{{2, 5}, {8, nFrames}},
},
{
metaVals: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"},
fIndices: nil,
},
}
// Run the tests.
for testn, test := range tests {
clip := &Clip{}
// Generate test frames.
for i := 0; i < nFrames; i++ {
clip.backing = append(clip.backing, []byte{byte(i), byte(i), byte(i)}...)
clip.frames = append(
clip.frames,
Frame{
Media: clip.backing[i*fSize : (i+1)*fSize],
idx: i * fSize,
Meta: map[string]string{
key: test.metaVals[i],
},
},
)
}
// Use function we're testing to get segments.
got := clip.SegmentForMeta(key, val)
// Now get expected segments using indices defined in the test.
var want []Clip
for _, indices := range test.fIndices {
// Calculate the indices for the backing array from the original clip.
backStart := clip.frames[indices.start].idx
backEnd := clip.frames[indices.end-1].idx + len(clip.frames[indices.end-1].Media)
// Use calculated indices to create Clip for current expected segment.
want = append(want, Clip{
frames: clip.frames[indices.start:indices.end],
backing: clip.backing[backStart:backEnd],
})
}
if !reflect.DeepEqual(got, want) {
t.Errorf("did not get expected result for test %v\nGot: %v\nWant: %v\n", testn, got, want)
}
}
}

9
go.mod
View File

@ -3,17 +3,12 @@ module bitbucket.org/ausocean/av
go 1.12
require (
bitbucket.org/ausocean/iot v1.2.4
bitbucket.org/ausocean/iot v1.2.5
bitbucket.org/ausocean/utils v1.2.6
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7
github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 // indirect
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884
github.com/mewkiz/flac v1.0.5
github.com/sergi/go-diff v1.0.0 // indirect
github.com/pkg/errors v0.8.1
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

11
go.sum
View File

@ -1,5 +1,8 @@
bitbucket.org/ausocean/iot v1.2.4 h1:M/473iQ0d4q+76heerjAQuqXzQyc5dZ3F7Bfuq6X7q4=
bitbucket.org/ausocean/iot v1.2.4/go.mod h1:5HVLgPHccW2PxS7WDUQO6sKWMgk3Vfze/7d5bHs8EWU=
bitbucket.org/ausocean/iot v1.2.5 h1:udD5X4oXUuKwdjO7bcq4StcDdjP8fJa2L0FnJJwF+6Q=
bitbucket.org/ausocean/iot v1.2.5/go.mod h1:dOclxXkdxAQGWO7Y5KcP1wpNfxg9oKUA2VqjJ3Le4RA=
bitbucket.org/ausocean/utils v1.2.4/go.mod h1:5JIXFTAMMNl5Ob79tpZfDCJ+gOO8rj7v4ORj56tHZpw=
bitbucket.org/ausocean/utils v1.2.6 h1:JN66APCV+hu6GebIHSu2KSywhLym4vigjSz5+fB0zXc=
bitbucket.org/ausocean/utils v1.2.6/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
@ -8,16 +11,21 @@ github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 h1:LdOc9B9Bj6LEsKiXSh
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7/go.mod h1:O5HA0jgDXkBp+jw0770QNBT8fsRJCbH7JXmM7wxLUBU=
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/adrianmo/go-nmea v1.1.1-0.20190109062325-c448653979f7/go.mod h1:HHPxPAm2kmev+61qmkZh7xgZF/7qHtSpsWppip2Ipv8=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-audio/aiff v0.0.0-20180403003018-6c3a8a6aff12/go.mod h1:AMSAp6W1zd0koOdX6QDgGIuBDTUvLa2SLQtm7d9eM3c=
github.com/go-audio/audio v0.0.0-20180206231410-b697a35b5608/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 h1:4sGU+UABMMsRJyD+Y2yzMYxq0GJFUsRRESI0P1gZ2ig=
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw=
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs=
github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ=
github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21 h1:Hc1iKlyxNHp3CV59G2E/qabUkHvEwOIJxDK0CJ7CRjA=
github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21/go.mod h1:LlQmBGkOuV/SKzEDXBPKauvN2UqCgzXO2XjecTGj40s=
github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc=
@ -29,16 +37,19 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e h1:3NIzz7weXhh3NToPgbtlQtKiVgerEaG4/nY2skGoGG0=
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e/go.mod h1:CaowXBWOiSGWEpBBV8LoVnQTVPV4ycyviC9IBLj8dRw=
github.com/yryz/ds18b20 v0.0.0-20180211073435-3cf383a40624/go.mod h1:MqFju5qeLDFh+S9PqxYT7TEla8xeW7bgGr/69q3oki0=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/sys v0.0.0-20190305064518-30e92a19ae4a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=

View File

@ -173,6 +173,10 @@ func TestErorHandling(t *testing.T) {
// TestFromFrame tests streaming from a single H.264 frame which is repeated.
func TestFromFrame(t *testing.T) {
testLog(0, "TestFromFrame")
testFrame := os.Getenv("RTMP_TEST_FRAME")
if testFrame == "" {
t.Skip("Skipping TestFromFrame since no RTMP_TEST_FRAME")
}
if testKey == "" {
t.Skip("Skipping TestFromFrame since no RTMP_TEST_KEY")
}
@ -181,7 +185,6 @@ func TestFromFrame(t *testing.T) {
t.Errorf("Dial failed with error: %v", err)
}
testFrame := os.Getenv("RTMP_TEST_FRAME")
b, err := ioutil.ReadFile(testFrame)
if err != nil {
t.Errorf("ReadFile failed with error: %v", err)

View File

@ -280,7 +280,7 @@ func newRtmpSender(url string, timeout uint, retries int, log func(lvl int8, msg
timeout: timeout,
retries: retries,
log: log,
ring: ring.NewBuffer(10, rbElementSize, 0),
ring: ring.NewBuffer(100, rbElementSize, 0),
done: make(chan struct{}),
}
s.wg.Add(1)