/**
* Class used to embed HD Audio data (AC3, DTS, DTSHD?, TRUEHD?) into a LPCM stream, according to IEC-61937, used by S/PDIF
*
* As of today (2011/07/15), only AC3 and DTS are working on my receiver, Denon AVR-1910
* DTS-HD cannot work because it needs a LPCM stream of 2 channels sampled at 192kHz = 6.1Mbits/s. (8 channels for TrueHD, 24Mbit/s)
* But PS3 seems to limit the HDMI output's samplerate at 48kHz for Non-HDCP content,
* so a downsample of an LPCM encoded audio track destroy the embedded data, alas.
*
* I disabled it for the time being (see variable skip_dtshd)
*
* Inspired by spdifenc.c from the FFmpeg team
*
* @author Arnaud Brochard
*/
package net.pms.util;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IEC61937AudioOutputStream extends FlowParserOutputStream {
private static final Logger LOGGER = LoggerFactory.getLogger(IEC61937AudioOutputStream.class);
private static int bits[] = new int[]{16, 16, 20, 20, 0, 24, 24};
private static int samplerates[] = new int[]{
0,
8000,
16000,
32000,
0,
0,
11025,
22050,
44100,
0,
0,
12000,
24000,
48000,
96000,
192000
};
private boolean ac3 = false;
private boolean dts = false;
private boolean dtsHD = false;
private int framesize;
private int blocks;
private int sample_rate;
private PCMAudioOutputStream out;
private int padding;
private byte preamble[];
private boolean usepreamble;
private byte dtshdpreamble[];
private int period;
public IEC61937AudioOutputStream(PCMAudioOutputStream out) {
super(out, 600000);
this.out = out;
out.swapOrderBits = 0;
neededByteNumber = 5000;
usepreamble = true;
}
@Override
protected void afterChunkSend() throws IOException {
padWithZeros(padding);
}
@Override
protected void analyzeBuffer(byte[] data, int off, int len) {
if (data[off + 0] == 100 && data[off + 1] == 88 && data[off + 2] == 32 && data[off + 3] == 37) {
LOGGER.trace("DTS-HD stray frame, skipping this one...");
//dtsHD = true;
streamableByteNumber = ((data[off + 6] & 0x0f) << 11) + ((data[off + 7] & 0xff) << 3) + ((data[off + 8] & 0xf0) >> 5) + 1;
discard = true;
} else if (data[off + 0] == 127 && data[off + 1] == -2 && data[off + 2] == -128 && data[off + 3] == 1) {
discard = false;
dts = true;
streamableByteNumber = framesize;
if (framesize == 0 || dtsHD) {
blocks = ((data[off + 4] & 0x01) << 6) + ((data[off + 5] & 0xfc) >> 2);
sample_rate = samplerates[((data[off + 8] >> 2) & 0x0f)];
framesize = ((data[off + 5] & 0x03) << 12) + ((data[off + 6] & 0xff) << 4) + ((data[off + 7] & 0xf0) >> 4) + 1;
int framesize_sup = 0;
int dts_rate = 48000;
boolean skip_dtshd = true;
if (!skip_dtshd && off + framesize + 3 < data.length && data[off + framesize] == 100 && data[off + framesize + 1] == 88 && data[off + framesize + 2] == 32 && data[off + framesize + 3] == 37) {
dtsHD = true;
dts_rate = 192000;
framesize_sup = ((data[off + framesize + 6] & 0x0f) << 11) + ((data[off + framesize + 7] & 0xff) << 3) + ((data[off + framesize + 8] & 0xf0) >> 5) + 1;
framesize += framesize_sup;
}
blocks++;
int pcm_wrapped_frame_size = blocks << 7;
if (usepreamble && preamble == null) {
int bitspersample = ((data[off + 11] & 0x01) << 2) + ((data[off + 12] & 0xfc) >> 6);
if (bitspersample < 7) {
LOGGER.trace("DTS bits per sample: " + bits[bitspersample]);
}
preamble = new byte[8];
preamble[1] = 114; // syncword1
preamble[0] = -8;
preamble[3] = 31; // syncword2
preamble[2] = 78;
if (dtsHD) {
preamble[4] = 0;
preamble[5] = 17; // DTS type IV = DTS-HD
} else {
preamble[4] = 0;
switch (blocks) {
case 512 >> 5:
preamble[5] = 11;
break;
case 1024 >> 5:
preamble[5] = 12;
break;
case 2048 >> 5:
preamble[5] = 13;
break;
default:
break;
}
}
if (dtsHD) {
period = dts_rate * (blocks << 5) / sample_rate;
byte subtype = 0x0;
switch (period) {
case 512:
subtype = 0x0;
break;
case 1024:
subtype = 0x1;
break;
case 2048:
subtype = 0x2;
break;
case 4096:
subtype = 0x3;
break;
case 8192:
subtype = 0x4;
break;
case 16384:
subtype = 0x5;
break;
default:
break;
}
preamble[4] = subtype;
dtshdpreamble = new byte[12];
dtshdpreamble[0] = 1;
dtshdpreamble[8] = -2;
dtshdpreamble[9] = -2;
}
}
if (out.sampleFrequency != dts_rate || out.nbchannels != 2 || out.bitsperSample != 16) {
out.nbchannels = 2;
out.sampleFrequency = dts_rate;
out.bitsperSample = 16;
//out.wavMode = true;
out.init();
}
if (dtsHD) {
pcm_wrapped_frame_size = period * 4;
}
if (framesize > pcm_wrapped_frame_size) {
framesize -= framesize_sup;
}
streamableByteNumber = framesize;
if (dtshdpreamble != null) {
dtshdpreamble[11] = (byte) (framesize & 0xff);
dtshdpreamble[10] = (byte) ((framesize >> 8) & 0xff);
framesize += dtshdpreamble.length;
}
if (preamble != null) {
int framesize_bits = framesize * 8;
preamble[7] = (byte) (framesize_bits & 0xff);
preamble[6] = (byte) ((framesize_bits >> 8) & 0xff);
}
padding = pcm_wrapped_frame_size - framesize - (preamble != null ? preamble.length : 0);
//LOGGER.debug("DTS spdif framesize: " + framesize + " / padding: " + padding);
}
} else if (data[off + 0] == 11 && data[off + 1] == 119) {
ac3 = true;
discard = false;
streamableByteNumber = framesize;
if (framesize == 0) {
// find the next ones
discard = true;
int a0 = data[off + 0];
int a1 = data[off + 1];
LOGGER.debug("Looking for AC3 framesize");
for (int i = 4; i < len - 4; i++) {
if (data[off + i] == a0 && data[off + i + 1] == a1) {
framesize = i;
streamableByteNumber = framesize;
int pcm_wrapped_frame_size = 6144; // padding_bytes = number_of_samples_in_the_audio_frame * 4 - frame_size
if (out != null) {
PCMAudioOutputStream pout = out;
pout.nbchannels = 2;
pout.sampleFrequency = 48000;
pout.bitsperSample = 16;
pout.init();
}
if (usepreamble) {
preamble = new byte[8];
padding = pcm_wrapped_frame_size - framesize - preamble.length;
preamble[1] = 114; // syncword1
preamble[0] = -8;
preamble[3] = 31; // syncword2
preamble[2] = 78;
preamble[5] = 1; // ac3
preamble[4] = 0;
int framesize_bits = framesize * 8;
preamble[7] = (byte) (framesize_bits % 256);
preamble[6] = (byte) (framesize_bits / 256);
} else {
padding = pcm_wrapped_frame_size - framesize;
}
LOGGER.debug("AC3 spdif framesize: " + framesize + " / padding: " + padding + " preamble[6]: " + preamble[6]);
discard = false;
break;
}
}
}
} else {
// DTS wrongly extracted ?... searching for start of the frame
for (int i = 3; i < 2020; i++) {
if (data.length > i && data[i - 3] == 127 && data[i - 2] == -2 && data[i - 1] == -128 && data[i] == 1) {
// skip DTS first frame as it's incomplete
discard = true;
streamableByteNumber = i - 3;
break;
} else if (data.length > i && data[i - 3] == 100 && data[i - 2] == 88 && data[i - 1] == 32 && data[i] == 37) {
// skip DTS-HD first frame
// stray HD frame ?
discard = true;
streamableByteNumber = i - 3;
break;
}
}
}
}
@Override
protected void beforeChunkSend() throws IOException {
if (preamble != null) {
writePayload(preamble);
}
if (dtshdpreamble != null) {
writePayload(dtshdpreamble);
}
}
public boolean isAc3() {
return ac3;
}
public boolean isDts() {
return dts;
}
public boolean isDtsHD() {
return dtsHD;
}
}