/*
* JAME 6.2.1
* http://jame.sourceforge.net
*
* Copyright 2001, 2016 Andrea Medeghini
*
* This file is part of JAME.
*
* JAME is an application for creating fractals and other graphics artifacts.
*
* JAME is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JAME is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JAME. If not, see <http://www.gnu.org/licenses/>.
*
*/
package net.sf.jame.queue.extensions.encoder;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.nextbreakpoint.ffmpeg4java.*;
import net.sf.jame.queue.encoder.EncoderContext;
import net.sf.jame.queue.encoder.EncoderException;
import net.sf.jame.queue.encoder.EncoderHook;
import net.sf.jame.queue.encoder.extension.AbstractEncoderExtensionRuntime;
import net.sf.jame.queue.encoder.extension.EncoderExtensionConfig;
/**
* @author Andrea Medeghini
*/
public abstract class FFmpegEncoderRuntime<T extends EncoderExtensionConfig> extends AbstractEncoderExtensionRuntime<T> {
private static final Logger logger = Logger.getLogger(FFmpegEncoderRuntime.class.getName());
private EncoderHook hook;
static {
FFmpeg4Java.av_register_all();
FFmpeg4Java.avcodec_register_all();
}
/**
* @see net.sf.jame.queue.encoder.extension.EncoderExtensionRuntime#setInterruptHook(net.sf.jame.queue.encoder.EncoderHook)
*/
@Override
public void setInterruptHook(final EncoderHook hook) {
this.hook = hook;
}
/**
* @see net.sf.jame.queue.encoder.extension.EncoderExtensionRuntime#encode(net.sf.jame.queue.encoder.EncoderContext, java.io.File)
*/
@Override
public void encode(final EncoderContext context, final File path) throws EncoderException {
fireStateChanged(0);
if (isMovieSupported()) {
AVFormatContext format_context = null;
AVOutputFormat output_format = null;
AVCodecContext codec_context = null;
AVRational time_base = null;
AVStream stream = null;
AVCodec codec = null;
AVFrame rgb_frame = null;
AVFrame yuv_frame = null;
SWIGTYPE_p_uint8_t rgb_bit_buffer = null;
SWIGTYPE_p_uint8_t yuv_bit_buffer = null;
SWIGTYPE_p_SwsContext sws_context = null;
try {
if (FFmpegEncoderRuntime.logger.isLoggable(Level.FINE)) {
FFmpegEncoderRuntime.logger.fine("Starting encoding...");
}
long time = System.currentTimeMillis();
int fps = context.getFrameRate();
int frame_count = context.getFrameCount();
int frame_width = context.getImageWidth();
int frame_height = context.getImageHeight();
format_context = FFmpeg4Java.avformat_alloc_context();
output_format = FFmpeg4Java.av_guess_format(getFormatName(), null, null);
if (format_context != null && output_format != null && !output_format.getVideo_codec().equals(AVCodecID.AV_CODEC_ID_NONE)) {
FFmpegEncoderRuntime.logger.info("Format is " + output_format.getLong_name());
format_context.setOformat(output_format);
time_base = new AVRational();
time_base.setNum(1);
time_base.setDen(fps);
codec = FFmpeg4Java.avcodec_find_encoder(output_format.getVideo_codec());
codec_context = FFmpeg4Java.avcodec_alloc_context3(codec);
if (codec != null && codec_context != null) {
codec_context.setCodec_id(codec.getId());
codec_context.setCodec_type(AVMediaType.AVMEDIA_TYPE_VIDEO);
codec_context.setPix_fmt(AVPixelFormat.AV_PIX_FMT_YUV420P);
codec_context.setWidth(frame_width);
codec_context.setHeight(frame_height);
codec_context.setTime_base(time_base);
codec_context.setBit_rate(40 * 1024);
codec_context.setGop_size(12);
codec_context.setMb_decision(2);
codec_context.setMax_b_frames(4);
codec_context.setB_quant_factor(0.1f);
codec_context.setI_quant_factor(0.1f);
if (codec_context.getCodec_id() == AVCodecID.AV_CODEC_ID_H264) {
// codec_context.setQmax(69);
// codec_context.setQmin(0);
// codec_context.setQcompress(0.6f);
// codec_context.setMax_qdiff(4);
// codec_context.setRefs(3);
// codec_context.setMe_cmp(FFmpeg4Java.FF_CMP_CHROMA);
// codec_context.setMe_range(16);
// codec_context.setMe_subpel_quality(7);
// codec_context.setTrellis(1);
// codec_context.setKeyint_min(25);
// codec_context.setGop_size(250);
// codec_context.setThread_count(0);
// codec_context.setPartitions(FFmpeg4Java.X264_PART_I4X4 | FFmpeg4Java.X264_PART_I8X8 | FFmpeg4Java.X264_PART_P8X8 | FFmpeg4Java.X264_PART_B8X8);
//codec_context.setFlags(FFmpeg4Java.AV_CODEC_FLAG_LOOP_FILTER);
codec_context.setProfile(FFmpeg4Java.FF_PROFILE_H264_HIGH);
}
if (codec_context.getCodec_id() == AVCodecID.AV_CODEC_ID_MPEG2VIDEO) {
codec_context.setProfile(FFmpeg4Java.FF_PROFILE_MPEG2_HIGH);
}
if (codec_context.getCodec_id() == AVCodecID.AV_CODEC_ID_MPEG4) {
codec_context.setProfile(FFmpeg4Java.FF_PROFILE_MPEG4_MAIN);
}
if (codec_context.getCodec_id() == AVCodecID.AV_CODEC_ID_MPEG4 || codec_context.getCodec_id() == AVCodecID.AV_CODEC_ID_MPEG2VIDEO || codec_context.getCodec_id() == AVCodecID.AV_CODEC_ID_MPEG1VIDEO) {
codec_context.setStrict_std_compliance(codec_context.getStrict_std_compliance() | FFmpeg4Java.FF_COMPLIANCE_STRICT);
}
codec_context.setFlags(codec_context.getFlags() | FFmpeg4Java.AV_CODEC_FLAG_GLOBAL_HEADER);
stream = FFmpeg4Java.avformat_new_stream(format_context, codec);
if (stream != null && format_context.getNb_streams() == 1) {
AVCodecParameters params = new AVCodecParameters();
params.setCodec_id(codec.getId());
params.setCodec_type(AVMediaType.AVMEDIA_TYPE_VIDEO);
params.setWidth(frame_width);
params.setHeight(frame_height);
stream.setTime_base(time_base);
stream.setCodecpar(params);
SWIGTYPE_p_uint8_t side_data = FFmpeg4Java.av_stream_new_side_data(stream, AVPacketSideDataType.AV_PKT_DATA_CPB_PROPERTIES, new AVCPBProperties().size_of());
AVCPBProperties props = AVCPBProperties.asTypePointer(SWIGTYPE_p_uint8_t.asVoidPointer(side_data));
props.setBuffer_size(frame_width * frame_height * 3 * 2);
side_data = FFmpeg4Java.av_stream_get_side_data(stream, AVPacketSideDataType.AV_PKT_DATA_CPB_PROPERTIES, null);
props = AVCPBProperties.asTypePointer(SWIGTYPE_p_uint8_t.asVoidPointer(side_data));
if (FFmpeg4Java.avcodec_open2(codec_context, codec, null) == 0) {
FFmpegEncoderRuntime.logger.info("Codec is " + codec.getName());
SWIGTYPE_p_p_void void_p_p = FFmpeg4Java.swig_from_p_to_p_p(AVIOContext.asVoidPointer(format_context.getPb()));
SWIGTYPE_p_p_AVIOContext avio_context_p_p = SWIGTYPE_p_p_AVIOContext.asTypePointer(SWIGTYPE_p_p_void.asVoidPointer(void_p_p));
if (FFmpeg4Java.avio_open2(avio_context_p_p, path.getAbsolutePath(), FFmpeg4Java.AVIO_FLAG_WRITE, null, null) >= 0) {
format_context.setPb(AVIOContext.asTypePointer(FFmpeg4Java.swig_from_p_p_to_p(SWIGTYPE_p_p_void.asTypePointer(SWIGTYPE_p_p_AVIOContext.asVoidPointer(avio_context_p_p)))));
sws_context = FFmpeg4Java.sws_getCachedContext(null, codec_context.getWidth(), codec_context.getHeight(), AVPixelFormat.AV_PIX_FMT_RGB24, codec_context.getWidth(), codec_context.getHeight(), AVPixelFormat.AV_PIX_FMT_YUV420P, FFmpeg4Java.SWS_BILINEAR, null, null, null);
if (sws_context != null) {
rgb_frame = FFmpeg4Java.av_frame_alloc();
yuv_frame = FFmpeg4Java.av_frame_alloc();
if (rgb_frame != null && yuv_frame != null) {
rgb_frame.setWidth(codec_context.getWidth());
rgb_frame.setHeight(codec_context.getHeight());
rgb_frame.setFormat(AVPixelFormat.AV_PIX_FMT_RGB24.swigValue());
yuv_frame.setWidth(codec_context.getWidth());
yuv_frame.setHeight(codec_context.getHeight());
yuv_frame.setFormat(AVPixelFormat.AV_PIX_FMT_YUV420P.swigValue());
FFmpeg4Java.av_image_alloc(rgb_frame.getData(), rgb_frame.getLinesize(), codec_context.getWidth(), codec_context.getHeight(), AVPixelFormat.AV_PIX_FMT_RGB24, 1);
FFmpeg4Java.av_image_alloc(yuv_frame.getData(), yuv_frame.getLinesize(), codec_context.getWidth(), codec_context.getHeight(), AVPixelFormat.AV_PIX_FMT_YUV420P, 1);
int rgb_bit_buffer_size = FFmpeg4Java.av_image_get_buffer_size(AVPixelFormat.AV_PIX_FMT_RGB24, codec_context.getWidth(), codec_context.getHeight(), 1);
int yuv_bit_buffer_size = FFmpeg4Java.av_image_get_buffer_size(AVPixelFormat.AV_PIX_FMT_YUV420P, codec_context.getWidth(), codec_context.getHeight(), 1);
rgb_bit_buffer = SWIGTYPE_p_uint8_t.asTypePointer(FFmpeg4Java.av_mallocz(rgb_bit_buffer_size));
yuv_bit_buffer = SWIGTYPE_p_uint8_t.asTypePointer(FFmpeg4Java.av_mallocz(yuv_bit_buffer_size));
if (rgb_bit_buffer != null && yuv_bit_buffer != null) {
FFmpeg4Java.av_image_fill_arrays(rgb_frame.getData(), rgb_frame.getLinesize(), rgb_bit_buffer, AVPixelFormat.AV_PIX_FMT_RGB24, codec_context.getWidth(), codec_context.getHeight(), 1);
FFmpeg4Java.av_image_fill_arrays(yuv_frame.getData(), yuv_frame.getLinesize(), yuv_bit_buffer, AVPixelFormat.AV_PIX_FMT_YUV420P, codec_context.getWidth(), codec_context.getHeight(), 1);
AVPacket packet = FFmpeg4Java.av_packet_alloc();
if (packet != null) {
packet.setStream_index(stream.getIndex());
FFmpeg4Java.avformat_write_header(format_context, null);
for (int frame = 0; frame < frame_count; frame++) {
if (FFmpegEncoderRuntime.logger.isLoggable(Level.FINE)) {
if (frame % 10 == 0) {
FFmpegEncoderRuntime.logger.fine(frame + " frames encoded...");
}
}
fireStateChanged(frame * 100 / (frame_count - 1));
byte[] data = context.getTileAsByteArray(frame, 0, 0, context.getImageWidth(), context.getImageHeight(), 3);
FFmpeg4Java.swig_set_bytes(rgb_bit_buffer, data);
FFmpeg4Java.sws_scale(sws_context, rgb_frame.getData(), rgb_frame.getLinesize(), 0, codec_context.getHeight(), yuv_frame.getData(), yuv_frame.getLinesize());
if (FFmpeg4Java.avcodec_send_frame(codec_context, yuv_frame) == 0) {
while (FFmpeg4Java.avcodec_receive_packet(codec_context, packet) == 0) {
FFmpeg4Java.av_packet_rescale_ts(packet, codec_context.getTime_base(), stream.getTime_base());
FFmpeg4Java.av_write_frame(format_context, packet);
}
}
if (hook.isInterrupted()) {
break;
}
}
if (FFmpeg4Java.avcodec_send_frame(codec_context, null) == 0) {
while (FFmpeg4Java.avcodec_receive_packet(codec_context, packet) == 0) {
FFmpeg4Java.av_packet_rescale_ts(packet, codec_context.getTime_base(), stream.getTime_base());
FFmpeg4Java.av_write_frame(format_context, packet);
}
}
FFmpeg4Java.av_write_trailer(format_context);
FFmpeg4Java.av_freep(AVPacket.asVoidPointer(packet));
packet.delete();
}
}
}
FFmpeg4Java.sws_freeContext(sws_context);
}
FFmpeg4Java.avio_close(format_context.getPb());
}
FFmpeg4Java.avcodec_close(codec_context);
}
}
}
}
if (!hook.isInterrupted()) {
if (FFmpegEncoderRuntime.logger.isLoggable(Level.FINE)) {
FFmpegEncoderRuntime.logger.fine(frame_count + " frames encoded.");
}
time = System.currentTimeMillis() - time;
if (FFmpegEncoderRuntime.logger.isLoggable(Level.INFO)) {
FFmpegEncoderRuntime.logger.info("Profile exported: elapsed time " + String.format("%3.2f", time / 1000.0d) + "s");
}
fireStateChanged(100);
}
}
catch (final Exception e) {
throw new EncoderException(e);
}
finally {
if (rgb_frame != null) {
FFmpeg4Java.av_freep(AVFrame.asVoidPointer(rgb_frame));
rgb_frame.delete();
rgb_frame = null;
}
if (yuv_frame != null) {
FFmpeg4Java.av_freep(AVFrame.asVoidPointer(yuv_frame));
yuv_frame.delete();
yuv_frame = null;
}
if (codec != null) {
codec.delete();
codec = null;
}
if (stream != null) {
stream.delete();
stream = null;
}
if (codec_context != null) {
codec_context.delete();
codec_context = null;
}
if (output_format != null) {
output_format.delete();
output_format = null;
}
if (format_context != null) {
format_context.delete();
format_context = null;
}
}
}
else {
throw new EncoderException("Can't encode the movie");
}
}
/**
* @return
*/
protected abstract String getFormatName();
}