mirror of https://bitbucket.org/ausocean/av.git
Merge branch 'master' into revid-audio
This commit is contained in:
commit
3f29f0c84e
|
@ -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
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# Default owners for everything in this repo.
|
||||
* @scruzin @saxon-milton
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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},
|
||||
},
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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},
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:])) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
return [2]uint64{}, errors.New("could only find one access unit in mpegts clip")
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)],
|
||||
},
|
||||
)
|
||||
}
|
|
@ -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
9
go.mod
|
@ -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
11
go.sum
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue