/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under The MIT license. */ package com.ttProject.container.ogg; import java.io.FileOutputStream; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import com.ttProject.container.IContainer; import com.ttProject.container.IWriter; import com.ttProject.container.ogg.type.Page; import com.ttProject.container.ogg.type.StartPage; import com.ttProject.frame.CodecType; import com.ttProject.frame.IAudioFrame; import com.ttProject.frame.IFrame; import com.ttProject.frame.IVideoFrame; import com.ttProject.frame.extra.AudioMultiFrame; import com.ttProject.frame.extra.VideoMultiFrame; import com.ttProject.frame.speex.SpeexFrame; import com.ttProject.frame.speex.type.CommentFrame; import com.ttProject.frame.vorbis.VorbisFrame; import com.ttProject.frame.vorbis.type.IdentificationHeaderFrame; import com.ttProject.unit.extra.bit.Bit1; import com.ttProject.unit.extra.bit.Bit5; import com.ttProject.unit.extra.bit.Bit8; /** * ogg page writer. * @author taktod * * こちらの動作としては、つぎつぎにframeデータをいれていくが、frameデータがいっぱいになったら次のoggPageに移動しないとだめ。 * 現在のページにつぎつぎとデータをいれていく。 */ public class OggPageWriter implements IWriter { /** logger */ private Logger logger = Logger.getLogger(OggPageWriter.class); /** */ private Map<Integer, OggPage> pageMap = new HashMap<Integer, OggPage>(); private final WritableByteChannel outputChannel; private FileOutputStream outputStream = null; /** speexのsample数を足していく(この動作はvorbisやtheoraの場合にかわってくるので、本来はここにあるべきではない。またマルチチャンネルの場合もこまったことになります。) */ private long addedSampleNum = 0; private List<CodecType> targetCodecTypeList = new ArrayList<CodecType>(); private Map<Integer, CodecType> processTrackMap = new HashMap<Integer, CodecType>(); /** * constructor * @param fileName * @throws Exception */ public OggPageWriter(String fileName) throws Exception { outputStream = new FileOutputStream(fileName); this.outputChannel = outputStream.getChannel(); } public OggPageWriter(FileOutputStream fileOutputStream) { this.outputChannel = fileOutputStream.getChannel(); } public OggPageWriter(WritableByteChannel outputChannel) { this.outputChannel = outputChannel; } /** * {@inheritDoc} */ @Override public void addContainer(IContainer container) throws Exception { } /** * {@inheritDoc} */ @Override public void addFrame(int trackId, IFrame frame) throws Exception { if(frame instanceof AudioMultiFrame) { for(IAudioFrame af : ((AudioMultiFrame) frame).getFrameList()) { addFrame(trackId, af); } return; } if(frame instanceof VideoMultiFrame) { for(IVideoFrame vf : ((VideoMultiFrame) frame).getFrameList()) { addFrame(trackId, vf); } return; } if(!processTrackMap.containsKey(trackId)) { // first frame for this track. // need to be setup. if(!targetCodecTypeList.remove(frame.getCodecType())) { // logger.warn("non target frame is detected.:" + frame.getCodecType()); return; } processTrackMap.put(trackId, frame.getCodecType()); switch(frame.getCodecType()) { case SPEEX: SpeexFrame sFrame = (SpeexFrame)frame; addFrame(trackId, sFrame.getHeaderFrame()); completePage(trackId); addFrame(trackId, new CommentFrame()); completePage(trackId); break; case VORBIS: /* * for vorbis * firstPage header unit(0x01 vorbis) * secondPage comment unit & setup unit * else normal vorbis data. */ VorbisFrame vFrame = (VorbisFrame)frame; IdentificationHeaderFrame headerFrame = vFrame.getHeaderFrame(); addFrame(trackId, headerFrame); completePage(trackId); // next for vorbis comment & setup Frame. addFrame(trackId, headerFrame.getCommentHeaderFrame()); addFrame(trackId, headerFrame.getSetupHeaderFrame()); completePage(trackId); /* * vorbis & theoraの場合はtheoraのpageとvorbisのページがごちゃ混ぜになっている感じ */ break; default: throw new Exception(frame.getCodecType() + " ogg writer is not supported now."); } } // TODO for the first frame, need to setup something. (headerFrame, commentFrame, setupFrame for vorbis like this.) if(frame instanceof IAudioFrame) { IAudioFrame aFrame = (IAudioFrame)frame; addedSampleNum += aFrame.getSampleNum(); OggPage targetPage = null; if(pageMap.get(trackId) == null) { targetPage = new StartPage(new Bit8(), new Bit1(), new Bit1(1), new Bit1(), new Bit5()); targetPage.setStreamSerialNumber(trackId); pageMap.put(trackId, targetPage); } else { targetPage = pageMap.get(trackId); } // set the data on page. targetPage.getFrameList().add(frame); // after add frame, need to update size. if(targetPage.getFrameList().size() >= 255) { completePage(trackId); } return; } } /** * {@inheritDoc} */ @Override public void prepareHeader(CodecType ...codecs) throws Exception { // hold the target codecType. for(CodecType codec : codecs) { targetCodecTypeList.add(codec); } } /** * {@inheritDoc} */ @Override public void prepareTailer() throws Exception { logger.info("writeTailer"); // set end the remain page. for(Integer key : pageMap.keySet()) { logger.info("target:" + key); OggPage page = pageMap.get(key); // update absoluteGranulePosition page.setAbsoluteGranulePosition(addedSampleNum); // set logic end flg page.setLogicEndFlag(true); // write pages. outputChannel.write(page.getData()); } // close stream. if(outputStream != null) { try { outputStream.close(); } catch(Exception e) { } outputStream = null; } } /** * set the current page complete. */ public void completePage(int trackId) throws Exception { OggPage page = pageMap.get(trackId); // update granulePosition(time position) page.setAbsoluteGranulePosition(addedSampleNum); // update output channel. outputChannel.write(page.getData()); int lastSequenceNo = page.getPageSequenceNo(); // prepare for next page. page = new Page(new Bit8(), new Bit1(), new Bit1(), new Bit1(), new Bit5()); page.setStreamSerialNumber(trackId); page.setPageSequenceNo(lastSequenceNo + 1); pageMap.put(trackId, page); } }