/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under GNU GENERAL PUBLIC LICENSE Version 3. */ package com.ttProject.xuggle.flv; import java.nio.ByteBuffer; import com.ttProject.media.aac.DecoderSpecificInfo; import com.ttProject.media.aac.frame.Aac; import com.ttProject.media.flv.Tag; import com.ttProject.media.flv.tag.AudioTag; import com.ttProject.media.flv.tag.VideoTag; import com.ttProject.media.h264.ConfigData; import com.ttProject.media.h264.DataNalAnalyzer; import com.ttProject.media.h264.Frame; import com.ttProject.media.h264.frame.PictureParameterSet; import com.ttProject.media.h264.frame.SequenceParameterSet; import com.ttProject.media.h264.frame.Slice; import com.ttProject.media.h264.frame.SliceIDR; import com.ttProject.nio.channels.ByteReadChannel; import com.ttProject.nio.channels.IReadChannel; import com.ttProject.util.BufferUtil; import com.xuggle.ferry.IBuffer; import com.xuggle.xuggler.ICodec; import com.xuggle.xuggler.IPacket; import com.xuggle.xuggler.IRational; import com.xuggle.xuggler.IStreamCoder; import com.xuggle.xuggler.ICodec.ID; import com.xuggle.xuggler.IStreamCoder.Direction; /** * flvTagをIPacketに変換するための動作 * @author taktod * */ public class FlvPacketizer { private VideoTag lastVideoTag; private SequenceParameterSet sps = null; private PictureParameterSet pps = null; private AudioTag lastAudioTag; private DecoderSpecificInfo dsi = null; /** * tagからIPacketを取り出す * @param tag * @return */ public IPacket getPacket(Tag tag, IPacket packet) throws Exception { if(tag instanceof VideoTag) { lastVideoTag = (VideoTag) tag; switch(lastVideoTag.getCodec()) { case JPEG: sps = null; pps = null; throw new RuntimeException("JPEGの変換は未実装です。"); case H263: sps = null; pps = null; return getH263Packet(lastVideoTag, packet); case SCREEN: sps = null; pps = null; throw new RuntimeException("SCREENの変換は未実装です。"); case ON2VP6: sps = null; pps = null; return getVP6Packet(lastVideoTag, packet); case ON2VP6_ALPHA: sps = null; pps = null; throw new RuntimeException("vp6 alphaの変換は未実装です。"); case SCREEN_V2: sps = null; pps = null; throw new RuntimeException("Screen V2の変換は未実装です。"); case AVC: return getAVCPacket(lastVideoTag, packet); default: break; } } else if(tag instanceof AudioTag) { lastAudioTag = (AudioTag) tag; switch(lastAudioTag.getCodec()) { case AAC: return getAACPacket(lastAudioTag, packet); case MP3_8: case MP3: dsi = null; return getMp3Packet(lastAudioTag, packet); case NELLY_16: case NELLY_8: case NELLY: dsi = null; return getNellyPacket(lastAudioTag, packet); case SPEEX: dsi = null; return getSpeexPacket(lastAudioTag, packet); case PCM: case ADPCM: case LPCM: case G711_A: case G711_U: case RESERVED: case DEVICE_SPECIFIC: dsi = null; throw new RuntimeException(lastAudioTag.getCodec() + "の変換は未実装です。"); default: return null; } } return null; } /** * h263用のIPacketを生成します。 * @param tag * @return */ private IPacket getH263Packet(VideoTag tag, IPacket packet) { if(packet == null) { packet = IPacket.make(); } ByteBuffer buffer = tag.getRawData(); int size = buffer.remaining(); IBuffer bufData = IBuffer.make(null, buffer.array(), 0, size); packet.setData(bufData); packet.setFlags(0); packet.setDts(tag.getTimestamp()); packet.setPts(tag.getTimestamp()); packet.setTimeBase(IRational.make(1, 1000)); packet.setComplete(true, size); packet.setKeyPacket(tag.isKeyFrame()); return packet; } /** * vp6用のIPacketを生成します。 * @param tag * @return */ private IPacket getVP6Packet(VideoTag tag, IPacket packet) { if(packet == null) { packet = IPacket.make(); } ByteBuffer buffer = tag.getRawData(); int size = buffer.remaining(); byte first = buffer.get(); byte[] data = new byte[size]; buffer.get(data, 0, size - 1); data[data.length - 1] = first; IBuffer bufData = IBuffer.make(null, data, 0, size); packet.setData(bufData); packet.setFlags(0); packet.setDts(tag.getTimestamp()); packet.setPts(tag.getTimestamp()); packet.setTimeBase(IRational.make(1, 1000)); packet.setComplete(true, size); packet.setKeyPacket(tag.isKeyFrame()); return packet; } /** * avc用のIPacketを生成します。 * @param tag * @return */ private IPacket getAVCPacket(VideoTag tag, IPacket packet) throws Exception { if(tag.isMediaSequenceHeader()) { // spsとppsを抜き出す。 ConfigData configData = new ConfigData(); IReadChannel rawData = new ByteReadChannel(tag.getRawData()); rawData.position(3); for(Frame nal : configData.getNals(rawData)) { if(nal instanceof SequenceParameterSet) { sps = (SequenceParameterSet) nal; } else if(nal instanceof PictureParameterSet) { pps = (PictureParameterSet) nal; } } return null; } if(tag.isEndOfSequence()) { return null; } if(sps == null || pps == null) { throw new RuntimeException("spsもしくはppsが決定していません。"); } // mshではないので、実データである。 if(packet == null) { packet = IPacket.make(); } ByteBuffer rawData = tag.getRawData(); rawData.position(3); // 3の位置に移動(ここからDataNalAnalyzerにいれるとnalが取り出せる) DataNalAnalyzer dataAnalyzer = new DataNalAnalyzer(); IReadChannel rawDataChannel = new ByteReadChannel(rawData); ByteBuffer buffer = null; Frame avcFrame = null; if(tag.isKeyFrame()) { while((avcFrame = dataAnalyzer.analyze(rawDataChannel)) != null) { if(avcFrame instanceof SliceIDR) { break; } } if(avcFrame == null) { throw new Exception("frameがありませんでした。"); } ByteBuffer spsData = sps.getData(); ByteBuffer ppsData = pps.getData(); ByteBuffer sliceIDRData = avcFrame.getData(); buffer = ByteBuffer.allocate(4 + spsData.remaining() + 4 + ppsData.remaining() + 4 + sliceIDRData.remaining()); buffer.putInt(1); buffer.put(spsData); buffer.putInt(1); buffer.put(ppsData); buffer.putInt(1); buffer.put(sliceIDRData); buffer.flip(); } else { while((avcFrame = dataAnalyzer.analyze(rawDataChannel)) != null) { if(avcFrame instanceof Slice) { break; } } if(avcFrame == null) { throw new Exception("frameがありませんでした。"); } ByteBuffer sliceData = avcFrame.getData(); buffer = ByteBuffer.allocate(4 + sliceData.remaining()); buffer.putInt(1); buffer.put(sliceData); buffer.flip(); } int size = buffer.remaining(); IBuffer bufData = IBuffer.make(null, buffer.array(), 0, size); packet.setData(bufData); packet.setFlags(1); packet.setDts(tag.getTimestamp()); packet.setPts(tag.getTimestamp()); packet.setTimeBase(IRational.make(1, 1000)); packet.setComplete(true, size); packet.setKeyPacket(tag.isKeyFrame()); return packet; } /** * 最後に応答したpacketに対応する映像coderを作成し応答する。 * @return */ public IStreamCoder createVideoDecoder() throws Exception { // まだデータがわからない場合はnullを返す if(lastVideoTag == null) { return null; } IStreamCoder decoder = null; switch(lastVideoTag.getCodec()) { case JPEG: throw new RuntimeException("JPEGの変換は未実装です。"); case H263: decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_FLV1); decoder.setTimeBase(IRational.make(1, 1000)); break; case SCREEN: throw new RuntimeException("SCREENの変換は未実装です。"); case ON2VP6: decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_VP6F); decoder.setTimeBase(IRational.make(1, 1000)); break; case ON2VP6_ALPHA: throw new RuntimeException("vp6 alphaの変換は未実装です。"); case SCREEN_V2: throw new RuntimeException("Screen V2の変換は未実装です。"); case AVC: decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_H264); decoder.setTimeBase(IRational.make(1, 1000)); break; default: return null; } if(decoder.open(null, null) < 0) { throw new Exception("デコーダーが開けませんでした。"); } return decoder; } private IPacket getMp3Packet(AudioTag tag, IPacket packet) throws Exception { if(packet == null) { packet = IPacket.make(); } ByteBuffer rawData = tag.getRawData(); int size = rawData.remaining(); IBuffer bufData = IBuffer.make(null, rawData.array(), 0, size); packet.setData(bufData); packet.setDts(tag.getTimestamp()); packet.setPts(tag.getTimestamp()); packet.setTimeBase(IRational.make(1, 1000)); packet.setComplete(true, size); return packet; } private IPacket getNellyPacket(AudioTag tag, IPacket packet) throws Exception { if(packet == null) { packet = IPacket.make(); } ByteBuffer rawData = tag.getRawData(); int size = rawData.remaining(); IBuffer bufData = IBuffer.make(null, rawData.array(), 0, size); packet.setData(bufData); packet.setDts(tag.getTimestamp()); packet.setPts(tag.getTimestamp()); packet.setTimeBase(IRational.make(1, 1000)); packet.setComplete(true, size); return packet; } private IPacket getSpeexPacket(AudioTag tag, IPacket packet) throws Exception { if(packet == null) { packet = IPacket.make(); } ByteBuffer rawData = tag.getRawData(); int size = rawData.remaining(); IBuffer bufData = IBuffer.make(null, rawData.array(), 0, size); packet.setData(bufData); packet.setDts(tag.getTimestamp()); packet.setPts(tag.getTimestamp()); packet.setTimeBase(IRational.make(1, 1000)); packet.setComplete(true, size); return packet; } /** * aac用のIPacketを生成します。 * @param tag * @return */ private IPacket getAACPacket(AudioTag tag, IPacket packet) throws Exception { if(tag.isMediaSequenceHeader()) { dsi = new DecoderSpecificInfo(); dsi.analyze(new ByteReadChannel(tag.getRawData())); return null; } if(dsi == null) { throw new RuntimeException("decoderSpecificInfoが決定していません"); } if(packet == null) { packet = IPacket.make(); } ByteBuffer rawData = tag.getRawData(); int size = rawData.remaining(); Aac aac = new Aac(size, dsi); aac.setData(rawData); ByteBuffer buffer = aac.getBuffer(); size = buffer.remaining(); IBuffer bufData = IBuffer.make(null, BufferUtil.toByteArray(buffer), 0, size); packet.setData(bufData); packet.setDts(tag.getTimestamp()); packet.setPts(tag.getTimestamp()); packet.setTimeBase(IRational.make(1, 1000)); packet.setComplete(true, size); return packet; } /** * 最後に応答したpacketに対応する音声coderを作成し応答する。 * @return */ public IStreamCoder createAudioDecoder() throws Exception { if(lastAudioTag == null) { return null; } IStreamCoder decoder = null; switch(lastAudioTag.getCodec()) { case AAC: // 単に2重にするだけでも動作するっぽいなんだこれ? decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_AAC); decoder.setSampleRate(lastAudioTag.getSampleRate()); decoder.setTimeBase(IRational.make(1, lastAudioTag.getSampleRate())); decoder.setChannels(lastAudioTag.getChannels()); break; case MP3: decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_MP3); decoder.setSampleRate(lastAudioTag.getSampleRate()); decoder.setTimeBase(IRational.make(1, lastAudioTag.getSampleRate())); decoder.setChannels(lastAudioTag.getChannels()); break; case MP3_8: decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_MP3); decoder.setSampleRate(8000); decoder.setTimeBase(IRational.make(1, lastAudioTag.getSampleRate())); decoder.setChannels(lastAudioTag.getChannels()); break; case NELLY: decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_NELLYMOSER); decoder.setSampleRate(lastAudioTag.getSampleRate()); decoder.setTimeBase(IRational.make(1, lastAudioTag.getSampleRate())); decoder.setChannels(lastAudioTag.getChannels()); break; case NELLY_8: decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_NELLYMOSER); decoder.setSampleRate(8000); decoder.setTimeBase(IRational.make(1, lastAudioTag.getSampleRate())); decoder.setChannels(lastAudioTag.getChannels()); break; case NELLY_16: // TODO 未チェック decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_NELLYMOSER); decoder.setSampleRate(16000); decoder.setTimeBase(IRational.make(1, lastAudioTag.getSampleRate())); decoder.setChannels(lastAudioTag.getChannels()); break; case SPEEX: // FLVのspeexは16000hz + 1Channelのみになってる。 decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_SPEEX); decoder.setSampleRate(16000); decoder.setTimeBase(IRational.make(1, lastAudioTag.getSampleRate())); decoder.setChannels(1); break; case PCM: case ADPCM: case LPCM: case G711_A: case G711_U: case RESERVED: case DEVICE_SPECIFIC: throw new RuntimeException(lastAudioTag.getCodec() + "の変換は未実装です。"); default: return null; } if(decoder.open(null, null) < 0) { throw new Exception("デコーダーが開けませんでした。"); } return decoder; } public static boolean isSameCodec(VideoTag tag, IStreamCoder coder) { switch(tag.getCodec()) { case H263: return coder.getCodecID() == ID.CODEC_ID_FLV1; case ON2VP6: return coder.getCodecID() == ID.CODEC_ID_VP6F; case AVC: return coder.getCodecID() == ID.CODEC_ID_H264; case JPEG: case SCREEN: case ON2VP6_ALPHA: case SCREEN_V2: throw new RuntimeException(tag.getCodec() + "の変換は未実装です。"); default: throw new RuntimeException("コーデックが識別できませんでした。"); } } public static boolean isSameCodec(AudioTag tag, IStreamCoder coder) { switch (tag.getCodec()) { case AAC: if((int)(coder.getSampleRate() / 1000) != (int)(tag.getSampleRate() / 1000)) { return false; } if(coder.getChannels() != tag.getChannels()) { return false; } return coder.getCodecID() == ID.CODEC_ID_AAC; case MP3: if((int)(coder.getSampleRate() / 1000) != (int)(tag.getSampleRate() / 1000)) { return false; } if(coder.getChannels() != tag.getChannels()) { return false; } return coder.getCodecID() == ID.CODEC_ID_MP3; case MP3_8: if((int)(coder.getSampleRate() / 1000) != 8) { return false; } if(coder.getChannels() != tag.getChannels()) { return false; } return coder.getCodecID() == ID.CODEC_ID_MP3; case NELLY: if((int)(coder.getSampleRate() / 1000) != (int)(tag.getSampleRate() / 1000)) { return false; } if(coder.getChannels() != tag.getChannels()) { return false; } return coder.getCodecID() == ID.CODEC_ID_NELLYMOSER; case NELLY_8: if((int)(coder.getSampleRate() / 1000) != 8) { return false; } if(coder.getChannels() != tag.getChannels()) { return false; } return coder.getCodecID() == ID.CODEC_ID_NELLYMOSER; case NELLY_16: if((int)(coder.getSampleRate() / 1000) != 16) { return false; } if(coder.getChannels() != tag.getChannels()) { return false; } return coder.getCodecID() == ID.CODEC_ID_NELLYMOSER; case PCM: case ADPCM: case LPCM: case G711_A: case G711_U: case RESERVED: case DEVICE_SPECIFIC: throw new RuntimeException(tag.getCodec() + "の変換は未実装です。"); default: throw new RuntimeException("コーデックが識別できませんでした。"); } } }