Additional work on Bink video codec

This commit is contained in:
Collin Smith
2020-10-05 00:49:04 -07:00
parent 2bbd3f0c9c
commit bb8a621d5d
5 changed files with 245 additions and 4 deletions

View File

@ -3,6 +3,8 @@ package com.riiablo.video;
import io.netty.buffer.ByteBuf;
import org.apache.commons.io.FileUtils;
import com.badlogic.gdx.audio.AudioDevice;
import com.riiablo.io.BitUtils;
import com.riiablo.io.ByteInput;
import com.riiablo.io.InvalidFormat;
@ -145,7 +147,7 @@ public class BIK {
return numTracks;
}
void decode(int frame) {
void decode(int frame, AudioDevice[] audio, float[][] out) {
final int offset = offsets[frame];
log.tracef("offset: +%x", offset);
final ByteBuf slice = buffer.slice(offset, offsets[frame + 1] - offset);
@ -157,10 +159,15 @@ public class BIK {
final int packetSize = in.readSafe32u();
log.trace("packetSize: {} bytes", packetSize);
final ByteInput audioPacket = in.readSlice(packetSize);
// final ByteInput audioPacket = in.readSlice(packetSize);
final ByteInput audioPacket = in;
final int numSamples = audioPacket.readSafe32u();
log.trace("numSamples: {}", numSamples);
BinkAudio track = tracks[i];
track.decode(audioPacket.unalign(), out);
audio[i].writeSamples(out[0], 0, numSamples);
log.trace("bytesRemaining: {} bytes", audioPacket.bytesRemaining());
} finally {
MDC.remove("track");

View File

@ -1,5 +1,9 @@
package com.riiablo.video;
import java.util.Arrays;
import org.apache.commons.math3.util.FastMath;
import com.riiablo.io.BitInput;
import com.riiablo.io.ByteInput;
import com.riiablo.io.InvalidFormat;
import com.riiablo.logger.LogManager;
@ -10,12 +14,13 @@ public class BinkAudio {
static final int SIZE = 0x0C;
static final int MAX_CHANNELS = 2;
static final int BLOCK_MAX_SIZE = MAX_CHANNELS << 11;
static final int FLAG_AUDIO_16BITS = 0x4000;
static final int FLAG_AUDIO_STEREO = 0x2000;
static final int FLAG_AUDIO_DCT = 0x1000;
private static final int[] BANDS = {
private static final int[] CRIT_FREQ = {
100, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720, 2000,
2320, 2700, 3150, 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500,
24500
@ -25,6 +30,10 @@ public class BinkAudio {
2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32, 64
};
private final float[] QUANTS;
private final int[] BANDS;
private final float[][] PREVIOUS = new float[MAX_CHANNELS][BLOCK_MAX_SIZE >>> 4];
final short numChannels;
final int sampleRate;
final int flags;
@ -34,6 +43,10 @@ public class BinkAudio {
final int overlapLen;
final int blockSize;
final int halfSampleRate;
final int numBands;
final float root;
boolean first;
BinkAudio(ByteInput in) {
log.trace("slicing {} bytes", SIZE);
@ -69,6 +82,31 @@ public class BinkAudio {
overlapLen = frameLen >> 4;
blockSize = (frameLen - overlapLen) * numChannels;
halfSampleRate = (sampleRate + 1) / 2;
root = frameLen / ((float) FastMath.sqrt(frameLen) * 32768f);
// if coded->id == RDFT then frameLen becomes 2f
QUANTS = new float[96];
for (int i = 0; i < 96; i++) {
/* constant is result of 0.066399999/log10(M_E) */
QUANTS[i] = (float) FastMath.exp(i * 0.15289164787221953823f) * root;
}
int numBands;
for (numBands = 1; numBands < 25 && halfSampleRate > CRIT_FREQ[numBands - 1]; numBands++);
this.numBands = numBands;
BANDS = new int[numBands + 1];
BANDS[0] = 2;
for (int i = 1; i < numBands; i++) {
BANDS[i] = (CRIT_FREQ[i - 1] * frameLen / halfSampleRate) & ~1;
}
BANDS[numBands] = frameLen;
first = true;
}
public float[][] createOut() {
return new float[MAX_CHANNELS][BLOCK_MAX_SIZE];
}
public String getFlagsString() {
@ -83,4 +121,91 @@ public class BinkAudio {
public boolean isMono() {
return (flags & FLAG_AUDIO_STEREO) == 0;
}
void decode(BitInput bits, float[][] out) {
int ch, i, j, k;
float q;
float[] quant = new float[25];
int width, coeff;
for (ch = 0; ch < numChannels; ch++) {
final float[] coeffs = out[ch];
coeffs[0] = readFloat29(bits) * root;
coeffs[1] = readFloat29(bits) * root;
for (i = 0; i < numBands; i++) {
final short value = bits.read8u();
quant[i] = QUANTS[Math.min(value, 95)];
}
k = 0;
q = quant[0];
// parse coefficients
i = 2;
while (i < frameLen) {
{
int v = bits.read1();
if (v != 0) {
v = bits.read7u(4);
j = i + RLE[v] << 3;
} else {
j = i + 8;
}
}
j = Math.min(j, frameLen);
width = bits.read7u(4);
if (width == 0) {
Arrays.fill(coeffs, i, coeffs.length, 0);
i = j;
while (BANDS[k] < i) {
q = quant[k++];
}
} else {
while (i < j) {
if (BANDS[k] == i) {
q = quant[k++];
}
coeff = bits.read31u(width);
if (coeff != 0) {
final int v = bits.read1();
coeffs[i] = (v != 0 ? -q : q) * coeff;
} else {
coeffs[i] = 0f;
}
i++;
}
}
}
if (false) { // dct stuff
coeffs[0] /= 0.5f;
//dct calc stuff
} else if (false) { // rdft decoder stuff
}
}
for (ch = 0; ch < numChannels; ch++) {
final float[] current = out[ch];
final float[] previous = PREVIOUS[ch];
int count = overlapLen * numChannels;
if (!first) {
j = ch;
for (i = 0; i < overlapLen; i++, j += numChannels) {
out[ch][i] = (previous[i] * (count - j) + current[i] * j) / count;
}
}
System.arraycopy(previous, 0, current, frameLen - overlapLen, overlapLen);
}
first = false;
}
static float readFloat29(BitInput bits) {
int power = bits.read32(5);
float f = FastMath.scalb(bits.read32(23), power - 23);
return bits.readBoolean() ? f : -f;
}
}

View File

@ -0,0 +1,50 @@
package com.riiablo.video;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.AudioDevice;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.utils.Disposable;
public class VideoPlayer implements Disposable {
private BIK bik;
private AudioDevice[] audio;
private float[][][] out;
public VideoPlayer() {
}
public void play(BIK bik) {
if (this.bik != null) {
throw new IllegalStateException("this.bik(" + this.bik + ") is not null");
}
this.bik = bik;
audio = new AudioDevice[bik.numTracks];
out = new float[bik.numTracks][][];
for (int i = 0, s = bik.numTracks; i < s; i++) {
final BinkAudio track = bik.track(i);
// final AudioDevice device = audio[i] = Gdx.audio.newAudioDevice(track.sampleRate, track.isMono());
final AudioDevice device = audio[i] = Gdx.audio.newAudioDevice(track.sampleRate, true);
device.setVolume(0.10f);
out[i] = track.createOut();
}
}
public void resize(int width, int height) {
}
int f = 0;
public void update(float delta) {
bik.decode(f++, audio, out[0]);
}
public void draw(Batch batch) {
}
@Override
public void dispose() {
}
}

View File

@ -38,7 +38,7 @@ public class BIKTest extends RiiabloTest {
FileHandle handle = Gdx.files.internal("test\\New_Bliz640x480.bik");
ByteBuf buffer = Unpooled.wrappedBuffer(handle.readBytes());
BIK bik = BIK.loadFromByteBuf(buffer);
bik.decode(0);
// bik.decode(0, null, null);
AudioDevice audio = Gdx.audio.newAudioDevice(44100, false);
// TODO: create video tool

View File

@ -0,0 +1,59 @@
package com.riiablo.video;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.GL20;
public class VideoTool extends ApplicationAdapter {
private static final String TAG = "VideoTool";
public static void main(String[] args) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = TAG;
config.resizable = false;
config.width = 640;
config.height = 480;
config.vSyncEnabled = false;
config.foregroundFPS = config.backgroundFPS = 144;
new LwjglApplication(new VideoTool(), config);
}
VideoTool() {}
VideoPlayer player;
@Override
public void create() {
Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
FileHandle handle = Gdx.files.internal("test\\New_Bliz640x480.bik");
ByteBuf buffer = Unpooled.wrappedBuffer(handle.readBytes());
BIK bik = BIK.loadFromByteBuf(buffer);
player = new VideoPlayer();
player.play(bik);
}
@Override
public void resize(int width, int height) {
player.resize(width, height);
}
@Override
public void render() {
System.out.println("update");
player.update(Gdx.graphics.getDeltaTime());
}
@Override
public void dispose() {
player.dispose();
}
}