All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aimmac23.node.RecordVideoCallable Maven / Gradle / Ivy

There is a newer version: 2.9
Show newest version
package com.aimmac23.node;

import java.io.File;
import java.util.concurrent.Callable;
import java.util.logging.Logger;

import com.aimmac23.exception.MissingFrameException;
import com.aimmac23.node.jna.EncoderInterface;
import com.aimmac23.node.jna.JnaLibraryLoader;
import com.sun.jna.Pointer;

public class RecordVideoCallable implements Callable {
	
	private static final Logger log = Logger.getLogger(RecordVideoCallable.class.getSimpleName());
	
	private final int targetFramerate;
	// how long we should sleep before recording another frame, if everything else took zero time
	private final int targetFramerateSleepTime;
	
	
	private volatile boolean shouldStop = false;

	private ScreenshotSource screenshotSource;
	
	static {
		JnaLibraryLoader.init();
	}
	
	public RecordVideoCallable(int targetFramerate, ScreenshotSource screenshotSource) {
		this.targetFramerate = targetFramerate;
		this.screenshotSource = screenshotSource;
		this.targetFramerateSleepTime = (int)((1.0 / targetFramerate) * 1000.0);
	}

	@Override
	public File call() throws Exception {
		int frames = 0;

		File outputFile = File.createTempFile("screencast", ".webm");
				
		EncoderInterface encoder = JnaLibraryLoader.getEncoder();
		
		log.info("Starting new recording at " + targetFramerate + " with resolution " 
		+ screenshotSource.getWidth() + "x" + screenshotSource.getHeight());
		Pointer context = encoder.create_context(outputFile.getCanonicalPath());
		int result = encoder.init_encoder(context, screenshotSource.getWidth(),
						screenshotSource.getHeight(), targetFramerate);
		
		handleVPXError(result, "Failed to create VPX Context", context);
		result = encoder.init_codec(context);
		
		handleVPXError(result, "Failed to create init VPX codec", context);

		result = encoder.init_image(context);
		if(result != 0) {
			throw new IllegalStateException("Failed to allocate memory for image buffer.");
		}
				
		log.info("Started recording to file: " + outputFile.getCanonicalPath());
		
		long excessTime = 0;

		long videoStartTime = System.currentTimeMillis();
		while(!shouldStop) {
			// how long to use the next frame for - should be 1 if we're not falling behind
			int frameDuration = 1 + (int)(excessTime / targetFramerateSleepTime);
			// if excessTime > TARGET_FRAMERATE_TIME. then we've just taken that into account
			excessTime = excessTime % targetFramerateSleepTime;
			long start = System.currentTimeMillis();
			
			try {
				result = screenshotSource.applyScreenshot(context);

				if(result != 0) {
					throw new IllegalStateException("Failed to convert frame to YUV format");
				}
				result = encoder.encode_next_frame(context, frameDuration);
				
				handleVPXError(result, "Failed to encode next VPX frame", context);				
			}
			catch(MissingFrameException e) {
				log.warning("Had to drop frame - reason: " + e.getMessage());
			}
			
			long finish = System.currentTimeMillis();
			frames++;
			long timeTaken = finish - start;
			// we're keeping up
			if(timeTaken < targetFramerateSleepTime) {
				Thread.sleep(targetFramerateSleepTime - timeTaken);
			}
			else {
				// we're falling behind, take that into account for the next frame
				excessTime += timeTaken;
			}
		}
		result = encoder.encode_finish(context);
		
		handleVPXError(result, "Failed to finalize video", context);
		
		long videoEndTime = System.currentTimeMillis();
		
		long duration = ((videoEndTime - videoStartTime) / 1000);
		if(duration != 0) {
			log.info("Finished recording - frames: " + frames + " duration: " +  duration +
					" seconds targetFps: " + targetFramerate + " actualFps: " + frames / duration);	
		}
		else {
			log.warning("Finished recording - video was zero length!");
		}
		return outputFile;
	}
	
	public void stopRecording() {
		shouldStop = true;
	}
	
	private void handleVPXError(int errorCode, String message, Pointer context) {
		if(errorCode != 0) {
			throw new IllegalStateException(message + ": " 
					+ JnaLibraryLoader.getLibVPX().vpx_codec_err_to_string(errorCode)
					+ " due to: " + JnaLibraryLoader.getEncoder().codec_error_detail(context));
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy