2020-01-26 00:39:13 -05:00
|
|
|
package d2video
|
|
|
|
|
|
|
|
import (
|
2021-01-11 20:23:43 -05:00
|
|
|
"errors"
|
2020-01-26 00:39:13 -05:00
|
|
|
"log"
|
2020-09-12 16:25:09 -04:00
|
|
|
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
2020-01-26 00:39:13 -05:00
|
|
|
)
|
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
// BinkVideoMode is the video mode type
|
2020-01-26 00:39:13 -05:00
|
|
|
type BinkVideoMode uint32
|
|
|
|
|
|
|
|
const (
|
2020-07-26 14:52:54 -04:00
|
|
|
// BinkVideoModeNormal is a normal video
|
2020-08-06 16:45:38 -04:00
|
|
|
BinkVideoModeNormal BinkVideoMode = iota
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
// BinkVideoModeHeightDoubled is a height-doubled video
|
2020-08-06 16:45:38 -04:00
|
|
|
BinkVideoModeHeightDoubled
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
// BinkVideoModeHeightInterlaced is a height-interlaced video
|
2020-08-06 16:45:38 -04:00
|
|
|
BinkVideoModeHeightInterlaced
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
// BinkVideoModeWidthDoubled is a width-doubled video
|
2020-08-06 16:45:38 -04:00
|
|
|
BinkVideoModeWidthDoubled
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
// BinkVideoModeWidthAndHeightDoubled is a width and height-doubled video
|
2020-08-06 16:45:38 -04:00
|
|
|
BinkVideoModeWidthAndHeightDoubled
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
// BinkVideoModeWidthAndHeightInterlaced is a width and height interlaced video
|
2020-08-06 16:45:38 -04:00
|
|
|
BinkVideoModeWidthAndHeightInterlaced
|
2020-01-26 00:39:13 -05:00
|
|
|
)
|
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
const (
|
|
|
|
numHeaderBytes = 3
|
|
|
|
bikHeaderStr = "BIK"
|
|
|
|
numAudioTrackUnknownBytes = 2
|
|
|
|
)
|
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
// BinkAudioAlgorithm represents the type of bink audio algorithm
|
2020-01-26 00:39:13 -05:00
|
|
|
type BinkAudioAlgorithm uint32
|
|
|
|
|
|
|
|
const (
|
2020-07-26 14:52:54 -04:00
|
|
|
// BinkAudioAlgorithmFFT is the FTT audio algorithm
|
2020-08-06 16:45:38 -04:00
|
|
|
BinkAudioAlgorithmFFT BinkAudioAlgorithm = iota
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
// BinkAudioAlgorithmDCT is the DCT audio algorithm
|
2020-08-06 16:45:38 -04:00
|
|
|
BinkAudioAlgorithmDCT
|
2020-01-26 00:39:13 -05:00
|
|
|
)
|
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
// BinkAudioTrack represents an audio track
|
2020-01-26 00:39:13 -05:00
|
|
|
type BinkAudioTrack struct {
|
|
|
|
AudioChannels uint16
|
|
|
|
AudioSampleRateHz uint16
|
|
|
|
Stereo bool
|
|
|
|
Algorithm BinkAudioAlgorithm
|
2020-07-26 14:52:54 -04:00
|
|
|
AudioTrackID uint32
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
// BinkDecoder represents the bink decoder
|
2020-01-26 00:39:13 -05:00
|
|
|
type BinkDecoder struct {
|
2020-07-26 14:52:54 -04:00
|
|
|
AudioTracks []BinkAudioTrack
|
|
|
|
FrameIndexTable []uint32
|
2020-09-08 15:58:35 -04:00
|
|
|
streamReader *d2datautils.StreamReader
|
2020-01-26 00:39:13 -05:00
|
|
|
fileSize uint32
|
|
|
|
numberOfFrames uint32
|
|
|
|
largestFrameSizeBytes uint32
|
|
|
|
VideoWidth uint32
|
|
|
|
VideoHeight uint32
|
|
|
|
FPS uint32
|
|
|
|
FrameTimeMS uint32
|
|
|
|
VideoMode BinkVideoMode
|
2020-07-26 14:52:54 -04:00
|
|
|
frameIndex uint32
|
|
|
|
videoCodecRevision byte
|
2020-01-26 00:39:13 -05:00
|
|
|
HasAlphaPlane bool
|
|
|
|
Grayscale bool
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
// Mask bit 0, as this is defined as a keyframe
|
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
// CreateBinkDecoder returns a new instance of the bink decoder
|
2021-01-11 20:23:43 -05:00
|
|
|
func CreateBinkDecoder(source []byte) (*BinkDecoder, error) {
|
2020-01-26 00:39:13 -05:00
|
|
|
result := &BinkDecoder{
|
2020-09-08 15:58:35 -04:00
|
|
|
streamReader: d2datautils.CreateStreamReader(source),
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
err := result.loadHeaderInformation()
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
return result, err
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
// GetNextFrame gets the next frame
|
2021-01-11 20:23:43 -05:00
|
|
|
func (v *BinkDecoder) GetNextFrame() error {
|
2020-08-06 16:45:38 -04:00
|
|
|
//nolint:gocritic // v.streamReader.SetPosition(uint64(v.FrameIndexTable[i] & 0xFFFFFFFE))
|
2021-01-11 20:23:43 -05:00
|
|
|
lengthOfAudioPackets, err := v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
samplesInPacket, err := v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
v.streamReader.SkipBytes(int(lengthOfAudioPackets) - 4) //nolint:gomnd // decode magic
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
log.Printf("Frame %d:\tSamp: %d", v.frameIndex, samplesInPacket)
|
|
|
|
|
|
|
|
v.frameIndex++
|
2021-01-11 20:23:43 -05:00
|
|
|
|
|
|
|
return nil
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
//nolint:gomnd,funlen,gocyclo // Decoder magic, can't help the long function length for now
|
|
|
|
func (v *BinkDecoder) loadHeaderInformation() error {
|
2020-01-26 00:39:13 -05:00
|
|
|
v.streamReader.SetPosition(0)
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
var err error
|
|
|
|
|
|
|
|
headerBytes, err := v.streamReader.ReadBytes(numHeaderBytes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if string(headerBytes) != bikHeaderStr {
|
|
|
|
return errors.New("invalid header for bink video")
|
|
|
|
}
|
|
|
|
|
|
|
|
v.videoCodecRevision, err = v.streamReader.ReadByte()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
v.fileSize, err = v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
v.numberOfFrames, err = v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
v.largestFrameSizeBytes, err = v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
const numBytesToSkip = 4 // Number of frames again?
|
|
|
|
|
|
|
|
v.streamReader.SkipBytes(numBytesToSkip)
|
|
|
|
|
|
|
|
v.VideoWidth, err = v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
v.VideoHeight, err = v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fpsDividend, err := v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fpsDivider, err := v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
v.FPS = uint32(float32(fpsDividend) / float32(fpsDivider))
|
|
|
|
v.FrameTimeMS = 1000 / v.FPS
|
2021-01-11 20:23:43 -05:00
|
|
|
|
|
|
|
videoFlags, err := v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
v.VideoMode = BinkVideoMode((videoFlags >> 28) & 0x0F)
|
|
|
|
v.HasAlphaPlane = ((videoFlags >> 20) & 0x1) == 1
|
|
|
|
v.Grayscale = ((videoFlags >> 17) & 0x1) == 1
|
2021-01-11 20:23:43 -05:00
|
|
|
|
|
|
|
numberOfAudioTracks, err := v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
v.AudioTracks = make([]BinkAudioTrack, numberOfAudioTracks)
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
for i := 0; i < int(numberOfAudioTracks); i++ {
|
2021-01-11 20:23:43 -05:00
|
|
|
v.streamReader.SkipBytes(numAudioTrackUnknownBytes)
|
|
|
|
|
|
|
|
v.AudioTracks[i].AudioChannels, err = v.streamReader.ReadUInt16()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
for i := 0; i < int(numberOfAudioTracks); i++ {
|
2021-01-11 20:23:43 -05:00
|
|
|
v.AudioTracks[i].AudioSampleRateHz, err = v.streamReader.ReadUInt16()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var flags uint16
|
|
|
|
|
|
|
|
flags, err = v.streamReader.ReadUInt16()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
v.AudioTracks[i].Stereo = ((flags >> 13) & 0x1) == 1
|
|
|
|
v.AudioTracks[i].Algorithm = BinkAudioAlgorithm((flags >> 12) & 0x1)
|
|
|
|
}
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
for i := 0; i < int(numberOfAudioTracks); i++ {
|
2021-01-11 20:23:43 -05:00
|
|
|
v.AudioTracks[i].AudioTrackID, err = v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
v.FrameIndexTable = make([]uint32, v.numberOfFrames+1)
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
for i := 0; i < int(v.numberOfFrames+1); i++ {
|
2021-01-11 20:23:43 -05:00
|
|
|
v.FrameIndexTable[i], err = v.streamReader.ReadUInt32()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
2021-01-11 20:23:43 -05:00
|
|
|
|
|
|
|
return nil
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|