/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under The MIT license. */ package com.ttProject.convert.ffmpeg; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import org.apache.log4j.Logger; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import com.ttProject.convert.IConvertManager; import com.ttProject.convert.ffmpeg.process.ProcessServer; import com.ttProject.convert.ffmpeg.worker.DataSendWorker; /** * ffmpegやavconvを利用してメディアデータを変換するプログラム * xuggleによる動作とちがって複数同時コンバートや、コンバートのデータの共有とかはできない。 * ただしマルチスレッド動作ができるので、パフォーマンスは良い * @author taktod */ public class FfmpegConvertManager implements IConvertManager { /** 動作ロガー */ private static final Logger logger = Logger.getLogger(FfmpegConvertManager.class); /** 対象プロセス */ private final Map<String, ProcessHandler> handlers = new HashMap<String, ProcessHandler>(); /** 動作pid */ private static final String pid; /** 動作ポート番号 */ private int portNumber; /** 動作サーバー */ private ProcessServer server; /** データ転送用のスレッド */ private Thread thread = null; /** データ送信処理 */ private DataSendWorker worker = null; /** データ転送用のqueue */ private LinkedBlockingQueue<ChannelBuffer> dataQueue = new LinkedBlockingQueue<ChannelBuffer>(); /** * 静的初期化 */ static { RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); pid = bean.getName().split("@")[0]; } /** * コンストラクタ * TODO この処理が重すぎて歪みがでる場合は関数にわけた方がよさそう。 * とりあえず利用側で回避している状態 * @throws Exception */ public FfmpegConvertManager() throws Exception { // このタイミングでサーバーをつくる ProcessServer processServer = null; // 処理可能なポート番号をみつける必要がある。 int portNumber = Integer.parseInt(pid); if(portNumber < 1000) { portNumber += 1000; } for(;portNumber < 65535;portNumber += 1000) { try { processServer = new ProcessServer(portNumber); break; } catch(Exception e) { ; } } if(portNumber > 65535) { logger.fatal("プロセス番号ベースでローカルサーバー用のポート番号が見つけられませんでした。"); throw new RuntimeException("ローカルサーバーの動作ポート番号が決まりませんでした。"); } // データが決まったので処理を続ける server = processServer; this.portNumber = portNumber; // threadをつくってデータを送る worker = new DataSendWorker(server, dataQueue); thread = new Thread(worker); // dataSendThread.setDaemon(true); thread.setName("ffmpegDataSendThread"); thread.start(); } /** * 動作handlerを取得する */ public ProcessHandler getProcessHandler(String name) { ProcessHandler handler = handlers.get(name); if(handler == null) { handler = new ProcessHandler(portNumber); handlers.put(name, handler); server.addKey(handler.getKey()); } return handler; } /** * {@inheritDoc} */ @Override public void start() { for(ProcessHandler handler : handlers.values()) { try { handler.executeProcess(); } catch (Exception e) { logger.error("プロセス起動で問題発生", e); } } } /** * {@inheritDoc} * TODO IReadChannelを渡したらあるだけずっと流し込みまくる形に方がいいかもしれない。まぁ、あとで検討しよう。 */ @Override public void applyData(ByteBuffer buffer) { // queueにデータをいれていく。 dataQueue.add(ChannelBuffers.copiedBuffer(buffer)); } /** * きちんとcloseされなかったときのために、finalizerでcloseしておきます。 */ @Override protected void finalize() throws Throwable { close(); super.finalize(); } /** * {@inheritDoc} */ @Override public void close() { // 内部処理workerを閉じておく。 for(String key : handlers.keySet()) { handlers.get(key).close(); } // サーバーを閉じておく if(server != null) { server.closeServer(); } // 内部threadの処理を抜けさせる if(worker != null) { worker.stop(); } // Threadの解放 if(thread != null) { thread.interrupt(); thread = null; } // 処理データqueueを閉じておく if(dataQueue != null) { dataQueue.clear(); dataQueue = null; } } }