/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under The MIT license.
*/
package com.ttProject.media.flv.tag;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import com.ttProject.media.flv.CodecType;
import com.ttProject.media.flv.Tag;
import com.ttProject.nio.channels.IReadChannel;
import com.ttProject.util.BufferUtil;
/**
* audioデータ
* @author taktod
*/
public class AudioTag extends Tag implements Cloneable {
/** コーデック */
private CodecType codec;
/** サンプリングレート */
private int sampleRate;
/** チャンネル数 */
private byte channels;
/** aacのmediaSequenceHeaderであるかどうか */
private boolean isMediaSequenceHeader = false;
/** データ本体 */
private ByteBuffer data;
/**
* コンストラクタ(データがファイルにない場合の処理)
*/
public AudioTag() {
super();
}
/**
* コンストラクタ(データがそもそもファイルにある場合の処理)
* @param size
* @param position
* @param timestamp
*/
public AudioTag(final int position, final int size, final int timestamp) {
super(position, size, timestamp);
}
/**
* メディアタグ用のbyteデータから必要な情報を抽出します。
* 12バイト目にあるやつ
* @param tagByte
* @return
*/
public boolean analyzeTagByte(byte tagByte) {
// サンプルレート解析
switch(tagByte & 0x0C) {
case 0x0C:
sampleRate = 44100;
break;
case 0x08:
sampleRate = 22050;
break;
case 0x04:
sampleRate = 11025;
break;
case 0x00:
sampleRate = 5512;
break;
}
if((tagByte & 0x01) == 0x01) {
channels = 2; // ステレオ
}
else {
channels = 1; // モノラル
}
codec = CodecType.getAudioCodecType(tagByte);
return (codec == CodecType.AAC);
}
public byte getTagByte() throws Exception {
byte tagByte = 0x00;
// codec判定
switch(codec) {
case PCM: tagByte = (byte)0x00;break;
case ADPCM: tagByte = (byte)0x12;break;
case MP3: tagByte = (byte)0x22;break;
case LPCM: tagByte = (byte)0x32;break; // pcmの場合は2がはいっているのはおかしいかも?
case NELLY_16: tagByte = (byte)0x42;break;
case NELLY_8: tagByte = (byte)0x52;break;
case NELLY: tagByte = (byte)0x62;break;
case G711_A: tagByte = (byte)0x72;break;
case G711_U: tagByte = (byte)0x82;break;
case RESERVED: tagByte = (byte)0x92;break;
case SPEEX: tagByte = (byte)0xB2;break;
case MP3_8: tagByte = (byte)0xE2;break;
case DEVICE_SPECIFIC: tagByte = (byte)0xF2;break;
case AAC: tagByte = (byte)0xA2;
if(data != null) {
setSize(data.remaining() + 2 + 15); // aacの場合はサイズがちょっとかわるので、上書きしておく。
}
break;
// case 12: // 不明
// case 13: // 未定義
default:
throw new RuntimeException("判定不能なコーデック");
}
// サンプリングレート
switch((int)(sampleRate / 1000)) {
case 44:
tagByte |= 0x0C;
break;
case 22:
tagByte |= 0x08;
break;
case 11:
tagByte |= 0x04;
break;
case 5:
break;
default:
throw new Exception("sampleRateが不正です。");
}
// サウンドチャンネル指定
if(channels == 2) {
tagByte |= 1;
}
return tagByte;
}
/**
* コーデックを設定
* @param codec
*/
public void setCodec(CodecType codec) {
this.codec = codec;
}
/**
* コーデックを参照
* @return
*/
public CodecType getCodec() {
return codec;
}
/**
* サンプルレートを設定
* @param rate
*/
public void setSampleRate(int rate) {
sampleRate = rate;
}
/**
* サンプルレートを参照
* @return
*/
public int getSampleRate() {
return sampleRate;
}
/**
* 音声チャンネル数を設定
* @param channels
*/
public void setChannels(byte channels) {
this.channels = channels;
}
/**
* チャンネル数を参照
* @return
*/
public byte getChannels() {
return channels;
}
/**
* mshであるか設定する
* @param flg
*/
public void setMSHFlg(boolean flg) {
isMediaSequenceHeader = flg;
}
/**
* データを登録しておく(メディアデータの本当の部分のみ)
* @param source
* @param size
*/
public void setData(IReadChannel source, int size) throws Exception {
data = BufferUtil.safeRead(source, size);
}
/**
* データを登録しておく(メディアデータの本当の部分のみ)
* @param buffer
*/
public void setRawData(ByteBuffer buffer) {
data = buffer.duplicate();
}
public ByteBuffer getRawData() {
return data.duplicate();
}
/**
* {@inheritDoc}
*/
@Override
public void analyze(IReadChannel ch, boolean atBegin) throws Exception {
// ファイルからの解析はあとでつくっておく。
super.analyze(ch, atBegin);
// 実データを読み込んでおく。
ch.position(getPosition() + 11);
// 1バイト読み込んで、コーデックを解析しておく。
analyzeTagByte(BufferUtil.safeRead(ch, 1).get());
int dataSize = getSize() - 16;
// AACの場合はさらに1バイト読み込んでおく。
if(codec == CodecType.AAC) {
isMediaSequenceHeader = (BufferUtil.safeRead(ch, 1).get() != 0x01);
dataSize --;
}
// 実データ部を読み込む
setData(ch, dataSize);
// tailを読み込む
if(BufferUtil.safeRead(ch, 4).getInt() != getSize() - 4) {
throw new Exception("tailByteの長さが狂ってます");
}
}
/**
* {@inheritDoc}
*/
@Override
public void writeTag(WritableByteChannel target) throws Exception {
target.write(getBuffer());
}
/**
* {@inheritDoc}
*/
@Override
public ByteBuffer getBuffer() throws Exception {
// データのベース位置を更新します。
data.position(0);
// tagを作成します。
// byte tagByte = 0x00;
// デフォルトサイズの更新
if(codec == CodecType.AAC) {
setSize(data.remaining() + 2 + 15);
}
else {
setSize(data.remaining() + 1 + 15);
}
// codec判定
/* switch(codec) {
// case PCM: tagByte = 0x00;
case ADPCM: tagByte = (byte)0x12;break;
case MP3: tagByte = (byte)0x22;break;
case PCM: tagByte = (byte)0x32;break; // pcmの場合は2がはいっているのはおかしいかも?
case NELLY_16: tagByte = (byte)0x42;break;
case NELLY_8: tagByte = (byte)0x52;break;
case NELLY: tagByte = (byte)0x62;break;
case G711_A: tagByte = (byte)0x72;break;
case G711_U: tagByte = (byte)0x82;break;
case RESERVED: tagByte = (byte)0x92;break;
case SPEEX: tagByte = (byte)0xB2;break;
case MP3_8: tagByte = (byte)0xE2;break;
case DEVICE_SPECIFIC: tagByte = (byte)0xF2;break;
case AAC: tagByte = (byte)0xA2;
setSize(data.remaining() + 2 + 15); // aacの場合はサイズがちょっとかわるので、上書きしておく。
break;
// case 12: // 不明
// case 13: // 未定義
default:
throw new RuntimeException("判定不能なコーデック");
}
// サンプリングレート
switch((int)(sampleRate / 1000)) {
case 44:
tagByte |= 0x0C;
break;
case 22:
tagByte |= 0x08;
break;
case 11:
tagByte |= 0x04;
break;
case 5:
break;
default:
throw new Exception("sampleRateが不正です。");
}
// サウンドチャンネル指定
if(channels == 2) {
tagByte |= 1;
}*/
// データの作成
ByteBuffer buffer = ByteBuffer.allocate(getSize());
// header
buffer.put(getHeaderBuffer((byte)0x08));
// tag
// buffer.put(tagByte);
buffer.put(getTagByte());
// aacの場合はmshの判定処理が必要
if(codec == CodecType.AAC) {
if(isMediaSequenceHeader) {
buffer.put((byte)0x00);
}
else {
buffer.put((byte)0x01);
}
}
// 実データ
buffer.put(data.duplicate());
// 終端データ
buffer.put(getTailBuffer());
// 読み込みモードに変更
buffer.flip();
return buffer;
}
/**
* mediaSequenceHeaderかどうか参照
* @return
*/
public boolean isMediaSequenceHeader() {
return isMediaSequenceHeader;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "audioTag:" + getTimestamp() + " codec:" + getCodec();
}
/**
* 同じタグを作成して応答します
*/
public AudioTag clone() {
AudioTag aTag = new AudioTag(0, getInitSize(), getTimestamp());
aTag.channels = channels;
aTag.codec = codec;
aTag.data = data;
aTag.isMediaSequenceHeader = isMediaSequenceHeader;
aTag.sampleRate = sampleRate;
return aTag;
}
}