com.github.hoary.javaav.Encoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of JavaAV Show documentation
Show all versions of JavaAV Show documentation
Java interface to FFmpeg
The newest version!
/*
* Copyright (C) 2013 Alex Andres
*
* This file is part of JavaAV.
*
* JavaAV 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 2 of the License,
* or (at your option) any later version (subject to the "Classpath"
* exception as provided in the LICENSE file that accompanied
* this code).
*
* JavaAV 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 JavaAV. If not, see .
*/
package com.github.hoary.javaav;
import com.googlecode.javacpp.BytePointer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static com.googlecode.javacv.cpp.avcodec.*;
import static com.googlecode.javacv.cpp.avutil.*;
public class Encoder extends Coder {
private final static Logger LOGGER = LogManager.getLogger(Encoder.class.getName());
/** Encoded audio packet buffer */
private BytePointer audioBuffer;
/** Encoded video packet buffer */
private BytePointer videoBuffer;
/** Encoded audio packet buffer size */
private int audioBufferSize;
/** Encoded video packet buffer size */
private int videoBufferSize;
/** The encoder picture format */
private PictureFormat dstVideoFormat;
/** The encoder audio format */
private AudioFormat audioFormat;
/** Source image structure */
private AVPicture picture;
/** Encoding video frame buffer */
private BytePointer pictureBuffer;
/** Audio re-sampler that is used to convert provided audio frames into encoder audio format */
private AudioResampler audioResampler;
/** Picture re-sampler that is used to convert provided pictures into encoder picture format */
private PictureResampler videoResampler;
/** Synchronization counter */
private long sync_opts;
public Encoder(CodecID codecId) throws JavaAVException {
this(Codec.getEncoderById(codecId), null);
}
public Encoder(Codec codec) throws JavaAVException {
this(codec, null);
}
Encoder(Codec codec, AVCodecContext avContext) throws JavaAVException {
super(codec, avContext);
if (this.avContext != null)
this.avContext.codec_id(codec.getCodec().id());
}
@Override
public void open(Map options) throws JavaAVException {
super.open(options);
if (codec.hasCapability(CodecCapability.EXPERIMENTAL))
this.avContext.strict_std_compliance(AVCodecContext.FF_COMPLIANCE_EXPERIMENTAL);
if (getMediaType() == MediaType.VIDEO) {
avFrame.pts(0); // required by libx264
createVideoBuffer();
dstVideoFormat = new PictureFormat(avContext.width(), avContext.height(), PixelFormat.byId(avContext.pix_fmt()));
}
else if (getMediaType() == MediaType.AUDIO) {
audioFormat = new AudioFormat();
audioFormat.setSampleFormat(SampleFormat.byId(avContext.sample_fmt()));
audioFormat.setChannelLayout(ChannelLayout.byId(avContext.channel_layout()));
audioFormat.setChannels(avContext.channels());
audioFormat.setSampleRate(avContext.sample_rate());
audioBufferSize = 256 * 4096;
audioBuffer = new BytePointer(av_malloc(audioBufferSize));
}
state = State.Opened;
}
@Override
public void close() {
// FIXME
// if (picture != null) {
// avpicture_free(picture);
// picture = null;
// }
if (pictureBuffer != null) {
av_free(pictureBuffer);
pictureBuffer = null;
}
if (videoBuffer != null) {
av_free(videoBuffer);
videoBuffer = null;
}
if (videoResampler != null) {
videoResampler.close();
videoResampler = null;
}
if (audioResampler != null) {
audioResampler.close();
audioResampler = null;
}
super.close();
}
public MediaPacket encodeVideo(VideoFrame frame) throws JavaAVException {
ByteBuffer imageBuffer = null;
if (frame != null) {
imageBuffer = frame.getData();
int width = frame.getWidth();
int height = frame.getHeight();
int step = imageBuffer.capacity() / (width * height) * width;
int pixelFormat = frame.getPixelFormat().value();
BytePointer data = new BytePointer(imageBuffer);
PictureFormat srcVideoFormat = frame.getPictureFormat();
if (!srcVideoFormat.equals(dstVideoFormat)) {
if (videoResampler == null)
videoResampler = new PictureResampler();
videoResampler.open(srcVideoFormat, dstVideoFormat);
int codecWidth = avContext.width();
int codecHeight = avContext.height();
avpicture_fill(picture, data, pixelFormat, width, height);
avpicture_fill(new AVPicture(avFrame), pictureBuffer, avContext.pix_fmt(), codecWidth, codecHeight);
videoResampler.resample(picture, new AVPicture(avFrame));
}
else {
avpicture_fill(new AVPicture(avFrame), data, pixelFormat, width, height);
avFrame.linesize(0, step);
}
}
av_init_packet(avPacket);
avPacket.data(videoBuffer);
avPacket.size(videoBufferSize);
avFrame.pts(sync_opts);
if (avFrame.interlaced_frame() != 0) {
if (avContext.codec().id() == AV_CODEC_ID_MJPEG)
avContext.field_order(avFrame.top_field_first() != 0 ? AV_FIELD_TT : AV_FIELD_BB);
else
avContext.field_order(avFrame.top_field_first() != 0 ? AV_FIELD_TB : AV_FIELD_BT);
}
else {
avContext.field_order(AV_FIELD_PROGRESSIVE);
}
avFrame.quality(avContext.global_quality());
if (avContext.me_threshold() == 0)
avFrame.pict_type(0);
if (avcodec_encode_video2(avContext, avPacket, imageBuffer == null ? null : avFrame, gotFrame) < 0)
throw new JavaAVException("Could not encode video packet.");
if (gotFrame[0] != 0) {
if (avPacket.pts() == AV_NOPTS_VALUE && (avContext.codec().capabilities() & CODEC_CAP_DELAY) == 0)
avPacket.pts(sync_opts);
MediaPacket mediaPacket = new MediaPacket(avPacket);
mediaPacket.setKeyFrame(avFrame.key_frame() != 0);
sync_opts++;
return mediaPacket;
}
else {
sync_opts++;
return null;
}
}
public MediaPacket[] encodeAudio(AudioFrame audioFrame) throws JavaAVException {
if (audioFormat == null)
throw new JavaAVException("Could not encode audio. No audio format specified.");
List packets = new ArrayList();
AudioFormat srcFormat = audioFrame.getAudioFormat();
AudioFrame[] frames;
// create re-sampler if sample formats does not match
if (!srcFormat.equals(audioFormat)) {
if (audioResampler == null) {
audioResampler = new AudioResampler();
audioResampler.open(srcFormat, audioFormat, avContext.frame_size());
}
frames = audioResampler.resample(audioFrame);
}
else {
frames = new AudioFrame[]{audioFrame};
}
for (AudioFrame frame : frames) {
avcodec_get_frame_defaults(avFrame);
for (int i = 0; i < frame.getPlaneCount(); i++) {
avFrame.data(i, frame.getPlane(i).position(0));
avFrame.linesize(i, frame.getPlane(i).limit());
}
avFrame.nb_samples(frame.getSampleCount());
avFrame.quality(avContext.global_quality());
MediaPacket mediaPacket = encodeAudioFrame(avFrame);
packets.add(mediaPacket);
}
return packets.toArray(new MediaPacket[0]);
}
public MediaPacket flushVideo() throws JavaAVException {
return encodeVideo(null);
}
public MediaPacket flushAudio() throws JavaAVException {
return encodeAudioFrame(null);
}
private MediaPacket encodeAudioFrame(AVFrame frame) throws JavaAVException {
av_init_packet(avPacket);
avPacket.data(audioBuffer);
avPacket.size(audioBufferSize);
if (frame != null) {
if (frame.pts() == AV_NOPTS_VALUE)
frame.pts(sync_opts);
sync_opts = frame.pts() + frame.nb_samples();
}
if (avcodec_encode_audio2(avContext, avPacket, frame, gotFrame) < 0)
throw new JavaAVException("Could not encode audio packet.");
if (gotFrame[0] != 0) {
AVPacket copyPacket = new AVPacket();
// need to copy packet, since the encoder uses common packet buffer
av_copy_packet(copyPacket, avPacket);
MediaPacket mediaPacket = new MediaPacket(copyPacket);
mediaPacket.setKeyFrame(avFrame.key_frame() != 0);
return mediaPacket;
}
else {
return null;
}
}
private void createVideoBuffer() throws JavaAVException {
picture = new AVPicture();
if (avpicture_alloc(picture, avContext.pix_fmt(), avContext.width(), avContext.height()) < 0)
throw new JavaAVException("Could not allocate decoding picture.");
// like in ffmpeg.c
videoBufferSize = Math.max(256 * 1024, 8 * avContext.width() * avContext.height());
videoBuffer = new BytePointer(av_malloc(videoBufferSize));
int size = avpicture_get_size(avContext.pix_fmt(), avContext.width(), avContext.height());
if ((pictureBuffer = new BytePointer(av_malloc(size))).isNull()) {
close();
throw new JavaAVException("Could not allocate picture buffer.");
}
}
public Codec getCodec() {
return codec;
}
public void setSampleRate(int samplerate) throws JavaAVException {
if (codec.getType() != MediaType.AUDIO)
throw new JavaAVException("Cannot set sample rate for non-audio codec.");
List supportedRates = Arrays.asList(codec.getSupportedSampleRates());
if (!supportedRates.contains(samplerate)) {
// pick the highest supported sample rate
samplerate = supportedRates.get(supportedRates.size() - 1);
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Sample rate {} is not supported by {}.", samplerate, getCodec().getName());
LOGGER.warn("-> Selected supported sample rate {}.", samplerate);
}
}
super.setSamplerate(samplerate);
}
public void setChannels(int channels) throws JavaAVException {
if (codec.getType() != MediaType.AUDIO)
throw new JavaAVException("Cannot set audio channels for non-audio codec.");
ChannelLayout[] channelLayouts = getCodec().getSupportedChannelLayouts();
if (channelLayouts != null) {
long layout = av_get_default_channel_layout(channels);
ChannelLayout channelLayout = ChannelLayout.byId(layout);
List layouts = Arrays.asList(channelLayouts);
if (channelLayout == null || !layouts.contains(channelLayout)) {
int oldChannels = channels;
channelLayout = ChannelLayout.STEREO;
channels = av_get_channel_layout_nb_channels(channelLayout.value());
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Codec {} does not support {} channels and channel layout {}.",
getCodec().getName(), oldChannels, channelLayout.asString());
LOGGER.warn("-> Selected {} channels as default with channel layout {}.", channels, channelLayout);
}
}
}
super.setAudioChannels(channels);
}
public void setFramerate(double framerate) throws JavaAVException {
if (codec.getType() != MediaType.VIDEO)
throw new JavaAVException("Cannot set frame rate for non-video codec.");
AVRational frameRate = av_d2q(framerate, 1001000);
AVRational supportedFramerates = codec.getCodec().supported_framerates();
if (supportedFramerates != null) {
int idx = av_find_nearest_q_idx(frameRate, supportedFramerates);
frameRate = supportedFramerates.position(idx);
}
super.setFramerate(av_q2d(frameRate));
}
public void setImageWidth(int width) throws JavaAVException {
if (codec.getType() != MediaType.VIDEO)
throw new JavaAVException("Cannot set image width for non-video codec.");
if (width < 0)
throw new JavaAVException("Image width cannot be < 0.");
super.setImageWidth(width);
}
public void setImageHeight(int height) throws JavaAVException {
if (codec.getType() != MediaType.VIDEO)
throw new JavaAVException("Cannot set image height for non-video codec.");
if (height < 0)
throw new JavaAVException("Image height cannot be < 0.");
super.setImageHeight(height);
}
public void setGOPSize(int gopSize) throws JavaAVException {
if (codec.getType() != MediaType.VIDEO)
throw new JavaAVException("Cannot set Group Of Pictures for non-video codec.");
if (gopSize < 0)
throw new JavaAVException("Group Of Pictures cannot be < 0.");
super.setGOPSize(gopSize);
}
public void setPixelFormat(PixelFormat format) throws JavaAVException {
if (codec.getType() != MediaType.VIDEO)
throw new JavaAVException("Cannot set pixel format for non-video codec.");
List supportedFormats = Arrays.asList(codec.getSupportedPixelFormats());
if (format == null || !supportedFormats.contains(format)) {
// pick the first supported pixel format
format = supportedFormats.get(0);
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("No valid pixel format provided for codec: {}", getCodec().getName());
LOGGER.warn("-> Selected default supported pixel format: {}", format);
}
}
super.setPixelFormat(format);
}
public void setSampleFormat(SampleFormat format) throws JavaAVException {
if (codec.getType() != MediaType.AUDIO)
throw new JavaAVException("Cannot set sample format for non-audio codec.");
if (format == null) {
// select one of supported sample formats ourselves
List supported = Arrays.asList(codec.getSupportedSampleFormats());
// prioritized formats
SampleFormat[] defaults = {
SampleFormat.S16, SampleFormat.S16P,
SampleFormat.S32, SampleFormat.S32P,
SampleFormat.FLT, SampleFormat.FLTP,
SampleFormat.DBL, SampleFormat.DBLP
};
for (SampleFormat defaultFormat : defaults) {
if (supported.contains(defaultFormat)) {
format = defaultFormat;
break;
}
}
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("No sample format provided for codec: {}", getCodec().getName());
LOGGER.warn("-> Selected default supported sample format: {}", format);
}
}
if (format == null)
throw new JavaAVException("Could not set sample format. No format available.");
super.setSampleFormat(format);
}
}