package org.jcodec.codecs.wav;
import static org.jcodec.codecs.wav.ReaderLE.readInt;
import static org.jcodec.codecs.wav.ReaderLE.readShort;
import static org.jcodec.codecs.wav.StringReader.readString;
import static org.jcodec.codecs.wav.WriterLE.writeInt;
import static org.jcodec.codecs.wav.WriterLE.writeShort;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jcodec.common.AudioFormat;
import org.jcodec.common.CountingInputStream;
import org.jcodec.common.IOUtils;
import org.jcodec.common.JCodecUtil;
import org.jcodec.common.model.ChannelLabel;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
public class WavHeader {
public static class FmtChunkExtended extends FmtChunk {
short cbSize;
short bitsPerCodedSample;
int channelLayout;
int guid;
public FmtChunkExtended(FmtChunk fmtChunk, short cbSize, short bitsPerCodedSample, int channelLayout, int guid) {
super(fmtChunk);
this.cbSize = cbSize;
this.bitsPerCodedSample = bitsPerCodedSample;
this.channelLayout = channelLayout;
this.guid = guid;
}
public static FmtChunk read(InputStream input) throws IOException {
FmtChunk fmtChunk = FmtChunk.read(input);
return new FmtChunkExtended(fmtChunk, readShort(input), readShort(input), readInt(input), readInt(input));
}
public void write(OutputStream out) throws IOException {
super.write(out);
writeShort(out, cbSize);
writeShort(out, bitsPerCodedSample);
writeInt(out, channelLayout);
writeInt(out, guid);
}
public int size() {
return super.size() + 12;
}
public ChannelLabel[] getLabels() {
List<ChannelLabel> labels = new ArrayList<ChannelLabel>();
for (int i = 0; i < mapping.length; i++) {
if ((channelLayout & (1 << i)) != 0)
labels.add(mapping[i]);
}
return labels.toArray(new ChannelLabel[0]);
}
}
static ChannelLabel[] mapping = new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT,
ChannelLabel.CENTER, ChannelLabel.LFE, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT,
ChannelLabel.FRONT_CENTER_LEFT, ChannelLabel.FRONT_CENTER_RIGHT, ChannelLabel.REAR_CENTER,
ChannelLabel.SIDE_LEFT, ChannelLabel.SIDE_RIGHT, ChannelLabel.CENTER, ChannelLabel.FRONT_LEFT,
ChannelLabel.CENTER, ChannelLabel.FRONT_RIGHT, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_CENTER,
ChannelLabel.REAR_RIGHT, ChannelLabel.STEREO_LEFT, ChannelLabel.STEREO_RIGHT };
public static class FmtChunk {
public short audioFormat;
public short numChannels;
public int sampleRate;
public int byteRate;
public short blockAlign;
public short bitsPerSample;
public FmtChunk() {
this.audioFormat = 1;
}
public FmtChunk(short audioFormat, short numChannels, int sampleRate, int byteRate, short blockAlign,
short bitsPerSample) {
this.audioFormat = audioFormat;
this.numChannels = numChannels;
this.sampleRate = sampleRate;
this.byteRate = byteRate;
this.blockAlign = blockAlign;
this.bitsPerSample = bitsPerSample;
}
public FmtChunk(FmtChunk other) {
this(other.audioFormat, other.numChannels, other.sampleRate, other.byteRate, other.blockAlign,
other.bitsPerSample);
}
public static FmtChunk read(InputStream input) throws IOException {
return new FmtChunk(readShort(input), readShort(input), readInt(input), readInt(input), readShort(input),
readShort(input));
}
public void write(OutputStream out) throws IOException {
writeShort(out, audioFormat);
writeShort(out, numChannels);
writeInt(out, sampleRate);
writeInt(out, byteRate);
writeShort(out, blockAlign);
writeShort(out, bitsPerSample);
}
public int size() {
return 16;
}
}
public String chunkId;
public int chunkSize;
public String format;
public FmtChunk fmt;
public int dataOffset;
public long dataSize;
public static final int WAV_HEADER_SIZE = 44;
public WavHeader(String chunkId, int chunkSize, String format, FmtChunk fmt, int dataOffset, long dataSize) {
this.chunkId = chunkId;
this.chunkSize = chunkSize;
this.format = format;
this.fmt = fmt;
this.dataOffset = dataOffset;
this.dataSize = dataSize;
}
public WavHeader(WavHeader header) {
throw new UnsupportedOperationException();
}
public WavHeader(WavHeader header, int rate) {
this(header);
fmt.sampleRate = rate;
}
public static WavHeader stereo48k() {
return stereo48k(0);
}
public static WavHeader stereo48k(long samples) {
return new WavHeader("RIFF", 40, "WAVE", new FmtChunk((short) 1, (short) 2, 48000, 48000 * 2 * 16 / 8,
(short) 4, (short) 16), 44, calcDataSize(1, 2, samples));
}
public static WavHeader mono48k(long samples) {
return new WavHeader("RIFF", 40, "WAVE", new FmtChunk((short) 1, (short) 1, 48000, 48000 * 1 * 16 / 8,
(short) 2, (short) 16), 44, calcDataSize(1, 2, samples));
}
public static WavHeader emptyWavHeader() {
return new WavHeader("RIFF", 40, "WAVE", new FmtChunk(), 44, 0);
}
public static WavHeader read(File file) throws IOException {
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(file));
return read(is);
} finally {
IOUtils.closeQuietly(is);
}
}
public static WavHeader read(InputStream in) throws IOException {
CountingInputStream cin = new CountingInputStream(in);
String chunkId = readString(cin, 4);
int chunkSize = readInt(cin);
String format = readString(cin, 4);
FmtChunk fmt = null;
if (!"RIFF".equals(chunkId) || !"WAVE".equals(format)) {
return null;
}
String fourcc;
int size = 0;
do {
fourcc = readString(cin, 4);
size = ReaderLE.readInt(cin);
if ("fmt ".equals(fourcc) && size >= 14 && size <= 1024 * 1024) {
switch (size) {
case 16:
fmt = FmtChunk.read(cin);
break;
case 18:
fmt = FmtChunk.read(cin);
StringReader.sureSkip(cin, 2);
break;
case 40:
fmt = FmtChunkExtended.read(cin);
StringReader.sureSkip(cin, 12);
break;
case 28:
fmt = FmtChunkExtended.read(cin);
break;
default:
throw new IllegalStateException("Don't know how to handle fmt size: " + size);
}
} else if (!"data".equals(fourcc)) {
StringReader.sureRead(cin, size);
}
} while (!"data".equals(fourcc));
return new WavHeader(chunkId, chunkSize, format, fmt, cin.getCount(), size);
}
public static WavHeader multiChannelWav(List<File> wavs) throws IOException {
return multiChannelWav(wavs.toArray(new File[0]));
}
public static WavHeader multiChannelWav(File... wavs) throws IOException {
WavHeader headers[] = new WavHeader[wavs.length];
for (int i = 0; i < wavs.length; i++) {
headers[i] = read(wavs[i]);
}
return multiChannelWav(headers);
}
/** Takes single channel wavs as input produces multi channel wav */
public static WavHeader multiChannelWav(WavHeader... wavs) {
WavHeader w = emptyWavHeader();
int totalSize = 0;
for (WavHeader wavHeader : wavs) {
totalSize += wavHeader.dataSize;
}
w.dataSize = totalSize;
FmtChunk fmt = wavs[0].fmt;
int bitsPerSample = fmt.bitsPerSample;
int bytesPerSample = bitsPerSample / 8;
int sampleRate = (int) fmt.sampleRate;
w.fmt.bitsPerSample = (short) bitsPerSample;
w.fmt.blockAlign = (short) (wavs.length * bytesPerSample);
w.fmt.byteRate = wavs.length * bytesPerSample * sampleRate;
w.fmt.numChannels = (short) wavs.length;
w.fmt.sampleRate = sampleRate;
return w;
}
public void write(OutputStream out) throws IOException {
long chunkSize;
if (dataSize <= 0xffffffffL) {
chunkSize = dataSize + 36;
} else {
chunkSize = 40;
}
out.write(JCodecUtil.asciiString("RIFF"));
writeInt(out, (int) chunkSize);
out.write(JCodecUtil.asciiString("WAVE"));
out.write(JCodecUtil.asciiString("fmt "));
writeInt(out, fmt.size());
fmt.write(out);
out.write(JCodecUtil.asciiString("data"));
if (dataSize <= 0xffffffffL) {
writeInt(out, (int) dataSize);
} else {
writeInt(out, 0);
}
}
public void writeExtended(OutputStream out) throws IOException {
long chunkSize;
if (dataSize <= 0xffffffffL) {
chunkSize = dataSize + 36;
} else {
chunkSize = 40;
}
out.write(JCodecUtil.asciiString("RIFF"));
writeInt(out, (int) chunkSize);
out.write(JCodecUtil.asciiString("WAVE"));
out.write(JCodecUtil.asciiString("fmt "));
writeInt(out, fmt.size());
fmt.write(out);
out.write(JCodecUtil.asciiString("data"));
if (dataSize <= 0xffffffffL) {
writeInt(out, (int) dataSize);
} else {
writeInt(out, 0);
}
}
public static long calcDataSize(int numChannels, int bytesPerSample, long samples) {
return samples * numChannels * bytesPerSample;
}
public static WavHeader create(AudioFormat af, int size) {
WavHeader w = emptyWavHeader();
w.dataSize = size;
FmtChunk fmt = new FmtChunk();
int bitsPerSample = af.getSampleSizeInBits();
int bytesPerSample = bitsPerSample / 8;
int sampleRate = (int) af.getSampleRate();
w.fmt.bitsPerSample = (short) bitsPerSample;
w.fmt.blockAlign = (short) (af.getFrameSize());
w.fmt.byteRate = (int) af.getFrameRate() * af.getFrameSize();
w.fmt.numChannels = (short) af.getChannels();
w.fmt.sampleRate = (int) af.getSampleRate();
return w;
}
public ChannelLabel[] getChannelLabels() {
if (fmt instanceof FmtChunkExtended) {
return ((FmtChunkExtended) fmt).getLabels();
} else {
switch (fmt.numChannels) {
case 1:
return new ChannelLabel[] { ChannelLabel.MONO };
case 2:
return new ChannelLabel[] { ChannelLabel.STEREO_LEFT, ChannelLabel.STEREO_RIGHT };
case 3:
return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.REAR_CENTER };
case 4:
return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.REAR_LEFT,
ChannelLabel.REAR_RIGHT };
case 5:
return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.CENTER,
ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT };
case 6:
return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.CENTER,
ChannelLabel.LFE, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT };
case 7:
return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.CENTER,
ChannelLabel.LFE, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT, ChannelLabel.REAR_CENTER };
case 8:
return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.CENTER,
ChannelLabel.LFE, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT, ChannelLabel.REAR_LEFT,
ChannelLabel.REAR_RIGHT };
default:
ChannelLabel[] labels = new ChannelLabel[fmt.numChannels];
Arrays.fill(labels, ChannelLabel.MONO);
return labels;
}
}
}
public AudioFormat getFormat() {
return new AudioFormat(fmt.sampleRate, fmt.bitsPerSample, fmt.numChannels, true, false);
}
}