package edu.washington.cs.oneswarm.ui.gwt.server.ffmpeg; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Semaphore; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo; import org.gudy.azureus2.plugins.download.Download; import org.gudy.azureus2.plugins.download.DownloadException; import org.gudy.azureus2.plugins.torrent.Torrent; import org.gudy.azureus2.plugins.torrent.TorrentException; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import edu.washington.cs.oneswarm.f2f.share.DownloadManagerStarter; import edu.washington.cs.oneswarm.f2f.share.DownloadManagerStarter.DownloadManagerStartListener; import edu.washington.cs.oneswarm.ui.gwt.CoreInterface; import edu.washington.cs.oneswarm.ui.gwt.RequiresShutdown; import edu.washington.cs.oneswarm.ui.gwt.rpc.OneSwarmConstants; import edu.washington.cs.oneswarm.ui.gwt.rpc.OneSwarmConstants.InOrderType; import edu.washington.cs.oneswarm.ui.gwt.server.ffmpeg.FFMpegException.ErrorType; import edu.washington.cs.oneswarm.ui.gwt.server.handlers.FileHandler; import edu.washington.cs.oneswarm.ui.gwt.server.handlers.SharedFileHandler; public class FFMpegWrapper implements RequiresShutdown { private static FFMpegException lastFFMpegException = null; private final static int AUDIO_RATE_DEFAULT = 128 * 1000; private final static int AUDIO_RATE_LOWER = 64 * 1000; private final static int AUDIO_RATE_LOWEST = 32 * 1000; private final static int AUDIO_RATE_MAX = 192 * 1000; public final static int CHUNK_SIZE = 1024; public final static int ENCODE_BUFFER = 10000; private final static int FFMPEG_UNITS = 1; /** * This class functions as a buffer between the input stream feed to FFMpeg, * and a slower source (like disk or http). This avoids having ffmpeg wait * for more data * * @author isdal * */ private static Logger logger = Logger.getLogger(FFMpegWrapper.class.getName()); public static boolean logToStdOut = true; private final static double STREAM_MAX_RATE = 1.15; /* * don't set the video rate to the full upload capacity setting this * parameter set the upload rate to be the full upload capacity, but the * video rate will be slightly lower to make the movie buffer a bit */ private static final float REMOTE_ACCESS_RATE_MARGIN = 0.85f; /* * leave a constant amount of head room as well, (updating the ui takes * 1-3KB/s) */ private static final int STREAM_MAX_USE_UPLOAD_LEAVE_KBPS = 5; private final static float STREAM_RATE_ERROR_MARGIN = 1.25f; // private OsgwtuiMain parent; /* * 128kbit/s audio is 16KB/s if upload limit is less than 4x that, decrease * audio quality * * if upload limit is less than 4x lower, decrease to lowest */ private final static int UPLOAD_CAP_TO_TRIGGER_LOWER_AUDIO_KBps = 64; private final static int UPLOAD_CAP_TO_TRIGGER_LOWEST_AUDIO_KBps = 32; private final static int VIDEO_RATE_DEFAULT = 1000 * 1000; private final static int VIDEO_RATE_MAX = 1500 * 1000; private final static int VIDEO_RATE_MIN = 64 * 8 * 1000; private final int audiorate; Process converter = null; private final CoreInterface coreInterface; private final Download download; private final DiskManagerFileInfo fileInfo; // private final File imageFile; private boolean quit = false; private final boolean remoteAccess; private final int streamByteRate; private final Torrent torrent; private final int videorate; private final double startAtSecond; public FFMpegWrapper(CoreInterface coreInterface, DiskManagerFileInfo sourceFile, Download download, boolean remote, double startAtByte) throws TorrentException { this.remoteAccess = remote; this.fileInfo = sourceFile; this.download = download; this.torrent = download.getTorrent(); // this.imageFile = coreInterface.getImageFile(torrent); this.coreInterface = coreInterface; this.audiorate = getAudioBitRate(); this.videorate = getVideoBitRate(audiorate); this.streamByteRate = (videorate + audiorate) / 8; this.startAtSecond = startAtByte / streamByteRate; coreInterface.addShutdownObject(this); } /** * for testing * * @throws FFMpegException */ private FFMpegWrapper(File file) throws FFMpegException { this.remoteAccess = false; this.fileInfo = null; this.download = null; this.torrent = null; // this.imageFile = null; this.coreInterface = null; this.audiorate = getAudioBitRate(); this.videorate = getVideoBitRate(audiorate); this.streamByteRate = (videorate + audiorate) / 8; this.startAtSecond = 0; } private void createNewFileConverter(File sourceFile, OutputStream destinationStream, MovieStreamInfo movieInfo) throws FFMpegException, InterruptedException { if (!sourceFile.exists()) { throw new FFMpegException(FFMpegException.ErrorType.FILE_NOT_FOUND, "File does not exist"); } String sourceFileName; try { sourceFileName = sourceFile.getCanonicalPath(); } catch (IOException e) { throw new FFMpegException(FFMpegException.ErrorType.FILE_NOT_FOUND, "Problem reading file", e); } logger.fine("using file: '" + sourceFileName + "'"); String[] mpegExecArray = getFFMpegExecArray(sourceFileName, movieInfo); try { converter = new ProcessBuilder(mpegExecArray).start(); } catch (IOException e) { FFMpegException mpegException = new FFMpegException( FFMpegException.ErrorType.FFMPEG_BIN_ERROR, "Unable to create FFMpeg process '" + mpegExecArray + "'", e); mpegException.setFFMpegExecArray(mpegExecArray); throw mpegException; } // make sure to dump anything showing up on stderr boolean showError = false; int bytesToStore = 5000; final StreamDumper streamDumper = new StreamDumper(converter.getErrorStream(), bytesToStore, showError); InputStream ffmpegOutput = converter.getInputStream(); FlvOutputBufferManager flvTool2ToWebBufferHandler = new FlvOutputBufferManager( ffmpegOutput, destinationStream, audiorate, videorate, false, movieInfo, startAtSecond); Thread t = new Thread(flvTool2ToWebBufferHandler); t.setName("BufferHandler"); t.setDaemon(true); t.start(); try { t.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } logger.fine("ffmpeg to web handler finished"); if (converter != null) { converter.destroy(); int converterExitVal = converter.waitFor(); if (converterExitVal == 255) { logger.fine("ffmpeg got killed (exit 255)"); } else if (converterExitVal != 0) { final FFMpegException mpegException = new FFMpegException( FFMpegException.ErrorType.FORMAT_ERROR, converterExitVal, streamDumper.getStoredOutput()); mpegException.setDataWritten(flvTool2ToWebBufferHandler.getTotal()); mpegException.setFFMpegExecArray(mpegExecArray); lastFFMpegException = mpegException; throw mpegException; } } } private void createNewStreamConverter(InputStream sourceStream, ServletOutputStream destinationStream, MovieStreamInfo movieInfo) throws FFMpegException, InterruptedException { String[] mpegExecArray = getFFMpegExecArray("-", movieInfo); try { converter = new ProcessBuilder(mpegExecArray).start(); } catch (IOException e) { throw new FFMpegException(FFMpegException.ErrorType.FFMPEG_BIN_ERROR, "Unable to create FFMpeg process: '" + mpegExecArray + "'", e); } OutputStream ffmpegInput = converter.getOutputStream(); // create a buffer for the input data, just to make sure that ffmpeg // never is waiting if it hasn't to DownloadBufferManager downloadStream = new DownloadBufferManager(sourceStream, fileInfo); // make sure to dump anything showing up on stderr boolean showError = false; int numBytesToSave = 5000; final StreamDumper streamDumper = new StreamDumper(converter.getErrorStream(), numBytesToSave, showError); // start the bufferhandler moving data from ffmpeg to the webserver InputStream ffmpegOutput = converter.getInputStream(); FlvOutputBufferManager ffmpegToWebBufferHandler = new FlvOutputBufferManager(ffmpegOutput, destinationStream, audiorate, videorate, true, movieInfo, startAtSecond); int position = 0; byte[] buf = new byte[CHUNK_SIZE]; int read = 0; // write as much as we can try { logger.fine("Starting to read"); while ((read = downloadStream.read(buf)) != -1 && ffmpegToWebBufferHandler.isRunning() && !quit) { ffmpegInput.write(buf, 0, read); position += read; } } catch (IOException e) { final FFMpegException mpegException = new FFMpegException( FFMpegException.ErrorType.FORMAT_ERROR_DOWNLOADING, "Error writing to ffmpeg (app closed??)", e); mpegException.setStdErr(streamDumper.getStoredOutput()); mpegException.setFFMpegExecArray(mpegExecArray); throw mpegException; } logger.fine("Convertion Done"); ffmpegToWebBufferHandler.quit(); try { Thread.sleep(1000); } catch (InterruptedException e) { } try { ffmpegInput.close(); ffmpegOutput.close(); } catch (IOException e) { e.printStackTrace(); } // close the process in case of exceptions if (converter != null) { converter.destroy(); int converterExitVal = converter.waitFor(); if (converterExitVal == 255) { logger.fine("ffmpeg got killed (exit 255)"); } else if (converterExitVal != 0) { final FFMpegException mpegException = new FFMpegException( FFMpegException.ErrorType.FORMAT_ERROR_DOWNLOADING, converterExitVal, streamDumper.getStoredOutput()); mpegException.setDataWritten(ffmpegToWebBufferHandler.getTotal()); lastFFMpegException = mpegException; mpegException.setFFMpegExecArray(mpegExecArray); throw mpegException; } } } private int getAudioBitRate() { int audioRate; if (remoteAccess) { /* * we need to think a bit here, we want to scale the audio rate * nicely so that audio won't use the entire stream */ int maxUpload = COConfigurationManager.getIntParameter("Max Upload Speed KBs"); if (maxUpload <= 0) { return AUDIO_RATE_MAX; } if (maxUpload < UPLOAD_CAP_TO_TRIGGER_LOWEST_AUDIO_KBps) { audioRate = AUDIO_RATE_LOWEST; } else if (maxUpload < UPLOAD_CAP_TO_TRIGGER_LOWER_AUDIO_KBps) { audioRate = AUDIO_RATE_LOWER; } else { audioRate = AUDIO_RATE_DEFAULT; } } else { audioRate = AUDIO_RATE_DEFAULT; } logger.finer("calculated audio rate, returning: " + audioRate); return audioRate; } private String[] getFFMpegExecArray(String sourceFile, MovieStreamInfo movieInfo) throws FFMpegException { // set the maximum rate allowed int videoRate = (int) (videorate / FFMPEG_UNITS); int audioRate = (int) (audiorate / FFMPEG_UNITS); int maxRate = (int) (videorate * STREAM_MAX_RATE / FFMPEG_UNITS); // the buffer size to use to make sure that the video rate is below max // rate, 2 seconds should be enough int bufSize = videorate * 2 / FFMPEG_UNITS; // int cpuCores = Runtime.getRuntime().availableProcessors(); // removing -re List<String> parameters = new LinkedList<String>(); parameters.add(FFMpegTools.getFFMpegPath()); // parameters.add("-threads"); // parameters.add("" + Runtime.getRuntime().availableProcessors()); if (startAtSecond > 0) { parameters.add("-ss"); parameters.add("" + startAtSecond); logger.fine("seeking to " + startAtSecond + " s"); } parameters.add("-i"); parameters.add(sourceFile); parameters.add("-f"); parameters.add("flv"); /* * add key frames every 50 frames */ parameters.add("-g"); parameters.add("50"); parameters.add("-b"); parameters.add("" + videoRate); parameters.add("-maxrate"); parameters.add("" + maxRate); parameters.add("-bufsize"); parameters.add("" + bufSize); /* * to make the conversion faster: scale down if res is larger than * player width or player height */ if (movieInfo.getResolutionX() > OneSwarmConstants.DEFAULT_WEB_PLAYER_WIDTH || movieInfo.getResolutionY() > OneSwarmConstants.DEFAULT_WEB_PLAYER_HEIGTH) { double resizeFactorWidth = movieInfo.getResolutionX() / (double) OneSwarmConstants.DEFAULT_WEB_PLAYER_WIDTH; double resizeFactorHeight = movieInfo.getResolutionY() / (double) OneSwarmConstants.DEFAULT_WEB_PLAYER_HEIGTH; double resizeFactor = Math.max(resizeFactorWidth, resizeFactorHeight); int newWidth = getResolution(movieInfo.getResolutionX(), resizeFactor); int newHeigth = getResolution(movieInfo.getResolutionY(), resizeFactor); parameters.add("-s"); parameters.add(newWidth + "x" + newHeigth); logger.finer("reducing resolution to: " + newWidth + "x" + newHeigth); } parameters.add("-acodec"); parameters.add("libmp3lame"); parameters.add("-ac"); parameters.add("2"); parameters.add("-ar"); parameters.add("44100"); parameters.add("-ab"); parameters.add("" + audioRate); if (movieInfo.isCropSet() && (movieInfo.getCropTop() > 0 || movieInfo.getCropBottom() > 0 || movieInfo.getCropLeft() > 0 || movieInfo.getCropRight() > 0)) { parameters.add("-croptop"); parameters.add("" + movieInfo.getCropTop()); parameters.add("-cropbottom"); parameters.add("" + movieInfo.getCropBottom()); parameters.add("-cropleft"); parameters.add("" + movieInfo.getCropLeft()); parameters.add("-cropright"); parameters.add("" + movieInfo.getCropRight()); } parameters.add("-"); StringBuilder cmd = new StringBuilder(); for (String string : parameters) { cmd.append("'" + string + "' "); } logger.fine("starting ffmpeg, parameters: \"" + cmd.toString() + "\""); return parameters.toArray(new String[parameters.size()]); } private int getResolution(long oldRes, double resizeFactor) { double newRes = oldRes / resizeFactor; int newResInt = (int) Math.round(newRes); if (newResInt % 2 != 0) { /* * many codec need res to be divisible by 2 */ if (newResInt - newRes > 0) { newResInt--; } else { newResInt++; } } logger.finer("resizing video, new size=" + newResInt + " (exact=" + newRes + "0)"); return newResInt; } private int getVideoBitRate(int aRate) { int videoRate; if (remoteAccess) { /* * basically we want to use all available bandwidth, but not more * than the MAX_RATE */ int maxUpload = COConfigurationManager.getIntParameter("Max Upload Speed KBs"); if (maxUpload <= 0) { return VIDEO_RATE_MAX; } int maxUploadBps = maxUpload * 8 * 1024; int available = Math.round((maxUploadBps - aRate) * REMOTE_ACCESS_RATE_MARGIN) - (STREAM_MAX_USE_UPLOAD_LEAVE_KBPS * 1000 * 8); String msg = "video rate calc: maxupload=" + maxUpload + " maxUploadBps=" + maxUploadBps + " audio=" + aRate + " avilable=" + available; logger.finest(msg); System.err.println(msg); videoRate = Math.max(VIDEO_RATE_MIN, Math.min(VIDEO_RATE_MAX, available)); } else { videoRate = COConfigurationManager.getIntParameter("OSGWT.flash bit rate", VIDEO_RATE_DEFAULT); } String msg = "calculated video rate, returning: " + videoRate; logger.finer(msg); System.err.println(msg); return videoRate; } private void handleFFMpegConvertFile(InOrderType type, HttpServletResponse response, HttpServletRequest request, MovieStreamInfo movieStreamInfo) throws IOException, FFMpegException, InterruptedException { response.setContentType(type.convertedMime); response.setStatus(HttpServletResponse.SC_OK); long contentLengthGuess = (long) Math.round(movieStreamInfo.getDuration() * streamByteRate * STREAM_RATE_ERROR_MARGIN); SharedFileHandler.setContentLength(response, contentLengthGuess); ServletOutputStream responseStream = response.getOutputStream(); logger.fine("file download is complete, just passing path to FFMpeg"); createNewFileConverter(fileInfo.getFile(), responseStream, movieStreamInfo); } private void handleFFMpegStream(InOrderType type, HttpServletResponse response, HttpServletRequest request, MovieStreamInfo movieStreamInfo, InputStream sourceStream) throws IOException, FFMpegException, InterruptedException { logger.fine("handleFFMpegStream"); response.setContentType(type.convertedMime); response.setStatus(HttpServletResponse.SC_OK); final double length = movieStreamInfo.getDuration() * streamByteRate; SharedFileHandler.setContentLength(response, length); ServletOutputStream responseStream = response.getOutputStream(); createNewStreamConverter(sourceStream, responseStream, movieStreamInfo); } public void process(HttpServletResponse response, HttpServletRequest request) { boolean downloadCompleted = false; try { downloadCompleted = fileInfo.getDownloaded() == fileInfo.getLength() && fileInfo.getFile().exists(); InOrderType type = InOrderType.getType(fileInfo.getFile().getName()); if (type == null) { if (downloadCompleted) { throw new FFMpegException(ErrorType.FORMAT_ERROR, "Unknown media type"); } else { throw new FFMpegException(ErrorType.FORMAT_ERROR_DOWNLOADING, "Unknown media type"); } } if (!type.convertNeeded) { new SharedFileHandler(fileInfo, download).process(response, request); } else if (downloadCompleted) { /* * we need to convert this, get the movie info so we can set the * movie and http content length */ MovieStreamInfo movieStreamInfo = FFMpegTools.getMovieInfo(fileInfo.getDownload() .getTorrent().getHash(), fileInfo.getFile()); if (movieStreamInfo.isFlashReady()) { new SharedFileHandler(fileInfo, download).process(response, request); } else { handleFFMpegConvertFile(type, response, request, movieStreamInfo); } } else { /* * need to convert it and it is not yet downloaded */ MovieStreamInfo movieStreamInfo = waitForDownloadAndGetMovieInfo(); InputStream sourceStream = download.getStats().getFileStream(fileInfo, movieStreamInfo.getBitRate()); if (movieStreamInfo.isFlashReady()) { new SharedFileHandler(fileInfo, download).process(response, request); } else { handleFFMpegStream(type, response, request, movieStreamInfo, sourceStream); } } } catch (org.mortbay.jetty.EofException e) { logger.fine("connection closed"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (FFMpegException e) { System.err.println("file='" + fileInfo.getFile() + "'"); System.err.println(e.toString()); try { if (e.getDataWritten() == 0) { System.err.println("sending error file instead"); if (!downloadCompleted) { new FileHandler().handle( "/oneswarmgwt/images/format_error_downloading.flv", request, response, 0); } else { new FileHandler().handle("/oneswarmgwt/images/format_error.flv", request, response, 0); } } else { System.err.println("error even though some data written: " + e.toString() + " / dataWritten: " + e.getDataWritten()); } if (COConfigurationManager.getBooleanParameter("oneswarm.beta.updates")) { // BackendErrorLog.get().logString("\n" + e.toString()); } } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (ServletException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (DownloadException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { shutdown(); coreInterface.removeShutdownObject(this); } } public void shutdown() { if (converter != null) { converter.destroy(); } this.quit = true; } private MovieStreamInfo waitForDownloadAndGetMovieInfo() throws InterruptedException, DownloadException, FFMpegException { // ok, file is not downloaded // check if the download is running final Semaphore s = new Semaphore(0); DownloadManagerStarter.startDownload(AzureusCoreImpl.getSingleton().getGlobalManager() .getDownloadManager(new HashWrapper(download.getTorrent().getHash())), new DownloadManagerStartListener() { public void downloadStarted() { s.release(); } }); s.acquire(); logger.fine("file is downloading"); while (!quit) { boolean done = download.getStats().isFirstLastMbDone(fileInfo, true); if (!done) { Thread.sleep(500); done = download.getStats().isFirstLastMbDone(fileInfo, false); System.out.println("waiting for download to complete enough to get video length"); } else { break; } } MovieStreamInfo movieStreamInfo = FFMpegTools.getMovieInfo(fileInfo.getDownload() .getTorrent().getHash(), fileInfo.getFile()); logger.fine("got movie info: " + movieStreamInfo.toString()); return movieStreamInfo; } /** * For testing */ // public static void main(String args[]) { // if (args.length != 1) { // System.err.println("Usage: FFMpegWrapper file"); // System.exit(1); // } // File file = new File(args[0]); // if (!file.exists()) { // System.err.println("'" + file.getAbsolutePath() + "' not found"); // System.exit(1); // } // if (file.isDirectory()) { // System.err.println("'" + file.getAbsolutePath() + "' is a folder"); // System.exit(1); // } // try { // MovieStreamInfo movieStreamInfo = FFMpegTools.getMovieInfo(file); // // /* // * create the converted file // */ // FFMpegWrapper mpegWrapper = new FFMpegWrapper(file); // File tempOutFile = new File("/tmp/ffmpegtest.flv"); // BufferedOutputStream out = new BufferedOutputStream(new // FileOutputStream(tempOutFile)); // mpegWrapper.createNewFileConverter(file, out, movieStreamInfo); // // /* // * and test // */ // IOHelper ioh2 = new IOHelper(tempOutFile); // ioh2.setDebug(true); // FlvHeader flvh2 = new FlvHeader(ioh2); // // ParseMeta parm = new ParseMeta(ioh2); // parm.findMetaTag(); // HashMap<String, Object> originalMetaData = parm.getMetaData(); // System.out.println("\nBEGIN modified meta data:\n" + // EmbeddedData.prettyPrintData(originalMetaData) + // "\nEND modified meta data"); // // // generate metadata // TagBroker tb = new TagBroker(ioh2, flvh2); // // MetaDataGen mdg = new MetaDataGen(tb, flvh2); // // mdg.buildOnLastSecond(); // // mdg.buildOnMetaData(); // // mdg.sealMetaData(true,); // // System.out.println(mdg.getMetaData().printMetaData()); // // } catch (FFMpegException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } catch (FileNotFoundException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // } class DownloadBufferManager implements Runnable { private final static int BYTE_STEAM_BUFFER_SIZE = 64 * CHUNK_SIZE; // decides how many chunks to read from disk at the same time, decreases // disk seeks private final static int CHUNK_BATCH_SIZE = 128; private ByteStreamBuffer buffer; private volatile boolean isReading = true; private InputStream source; public DownloadBufferManager(InputStream source, DiskManagerFileInfo fileInfo) { this.buffer = new ByteStreamBuffer(BYTE_STEAM_BUFFER_SIZE, CHUNK_SIZE); this.source = source; Thread t = new Thread(this); t.setName("DownloadBuffer"); t.setDaemon(true); t.start(); } public void close() { try { source.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public int read(byte[] buf) { if (!isReading && buffer.isEmpty()) { return -1; } try { return buffer.read(buf); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return -1; } public void run() { int len = 0; int total = 0; byte[] buf = new byte[CHUNK_SIZE * CHUNK_BATCH_SIZE]; try { System.out.println("DownloadBufferManager started"); while ((len = source.read(buf, 0, buf.length)) != -1) { buffer.write(buf, len); // System.out.println("wrote " + len + " bytes"); total += len; } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); if (e.getMessage().contains("read position")) { // ok, it seems like the sequential reader quit long position = Long.parseLong(e.getMessage().split("=")[1]); logger.fine("got exception from the sequential stream reader"); if (fileInfo.getLength() == fileInfo.getDownloaded()) { logger.fine("Download is complete, continuing from the file instead, starting at pos: " + position); try { BufferedInputStream in = new BufferedInputStream(new FileInputStream( fileInfo.getFile())); in.skip(position); while ((len = in.read(buf, 0, buf.length)) != -1) { buffer.write(buf, len); // System.out.println("wrote " + len + " // bytes"); total += len; } } catch (FileNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e3) { // TODO Auto-generated catch block e3.printStackTrace(); } catch (InterruptedException e4) { // TODO Auto-generated catch block e4.printStackTrace(); } } } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.isReading = false; System.out.println("DownloadBufferManager stopped"); } } class StreamMover implements Runnable { private final OutputStream dst; private final InputStream src; public StreamMover(InputStream src, final OutputStream dst) { this.src = new BufferedInputStream(src); this.dst = new BufferedOutputStream(dst); Thread t = new Thread(this); t.setName("Stream mover"); t.start(); } public void run() { int len = 0; int total = 0; byte[] buffer = new byte[FFMpegWrapper.CHUNK_SIZE]; try { while ((len = src.read(buffer)) != -1) { // System.out.println("trying to write " + len // + " to flvtool2 (total=" + total + ")"); total += len; dst.write(buffer, 0, len); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Stream mover stopped"); } } public static FFMpegException getLastException() { return lastFFMpegException; } }