
org.libav.audio.AudioFrameEncoder Maven / Gradle / Ivy
/*
* Copyright (C) 2012 Ondrej Perutka
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*/
package org.libav.audio;
import com.sun.jna.Pointer;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.libav.IEncoder;
import org.libav.LibavException;
import org.libav.avcodec.*;
import org.libav.avcodec.bridge.IAVCodecLibrary;
import org.libav.avformat.IStreamWrapper;
import org.libav.avutil.bridge.AVMediaType;
import org.libav.avutil.bridge.AVSampleFormat;
import org.libav.avutil.bridge.IAVUtilLibrary;
import org.libav.bridge.LibraryManager;
import org.libav.c.bridge.ICLibrary;
import org.libav.data.IPacketConsumer;
import org.libav.util.Rational;
/**
* Audio frame encoder.
*
* @author Ondrej Perutka
*/
public class AudioFrameEncoder implements IEncoder {
private static final IAVUtilLibrary utilLib = LibraryManager.getInstance().getAVUtilLibraryWrapper().getLibrary();
private static final ICLibrary cLib = LibraryManager.getInstance().getCLibrary();
public static final int DEFAULT_OUTPUT_BUFFER_SIZE = 200000;
private IStreamWrapper stream;
private ICodecContextWrapper cc;
private IFrameWrapper tmpFrame;
private int offset;
private int outputBufferSize;
private Pointer outputBuffer;
private IPacketWrapper packet;
private Rational ptsTransformBase;
private long ptsOffset;
private final Set consumers;
/**
* Create a new audio frame wncoder for the given audio stream.
*
* @param stream an audio stream
* @throws LibavException if the encoder cannot be created for some reason
* (caused by the Libav)
*/
public AudioFrameEncoder(IStreamWrapper stream) throws LibavException {
this.stream = stream;
cc = stream.getCodecContext();
cc.clearWrapperCache();
if (cc.getCodecType() != AVMediaType.AVMEDIA_TYPE_AUDIO)
throw new IllegalArgumentException("not an audio stream");
tmpFrame = FrameWrapperFactory.getInstance().allocFrame();
tmpFrame.setData(0, malloc(IAVCodecLibrary.AVCODEC_MAX_AUDIO_FRAME_SIZE + IAVCodecLibrary.FF_INPUT_BUFFER_PADDING_SIZE));
tmpFrame.setLineSize(0, 0);
outputBufferSize = DEFAULT_OUTPUT_BUFFER_SIZE;
outputBuffer = malloc(outputBufferSize);
packet = PacketWrapperFactory.getInstance().alloc();
ptsTransformBase = stream.getTimeBase().mul(1000).invert();
ptsOffset = -1;
consumers = Collections.synchronizedSet(new HashSet());
}
@Override
public ICodecContextWrapper getCodecContext() {
return cc;
}
@Override
public IStreamWrapper getStream() {
return stream;
}
@Override
public synchronized void close() {
cc.close();
if (outputBuffer != null)
utilLib.av_free(outputBuffer);
if (packet != null)
packet.free();
if (tmpFrame != null)
utilLib.av_free(tmpFrame.getData()[0]);
outputBuffer = null;
packet = null;
tmpFrame = null;
}
@Override
public boolean isClosed() {
return outputBuffer == null;
}
/**
* Get size of the output buffer in bytes (it is the buffer passed to the
* encoding function).
*
* @return size of the output buffer
*/
public int getOutputBufferSize() {
return outputBufferSize;
}
/**
* Set size of the output buffer (it is the buffer passed to the encoding
* function). DO NOT USE this method until you know what you are doing.
*
* @param outputBufferSize a size in bytes
*/
public synchronized void setOutputBufferSize(int outputBufferSize) {
if (isClosed())
return;
utilLib.av_free(outputBuffer);
outputBuffer = malloc(outputBufferSize);
this.outputBufferSize = outputBufferSize;
}
private Pointer malloc(int size) {
Pointer ptr = utilLib.av_malloc(size);
if (ptr == null)
throw new OutOfMemoryError("not enough memory for the audio frame encoder");
return ptr;
}
@Override
public synchronized void processFrame(Object producer, IFrameWrapper frame) throws LibavException {
if (isClosed())
return;
if (cc.isClosed())
openCodecContext();
IPacketWrapper p = encodeFrame(frame);
if (p != null)
sendPacket(p);
}
@Override
public synchronized void flush() throws LibavException {
if (isClosed())
return;
if (cc.isClosed())
openCodecContext();
IPacketWrapper p;
while ((p = flushFrame()) != null)
sendPacket(p);
}
private void openCodecContext() throws LibavException {
cc.clearWrapperCache();
cc.open(CodecWrapperFactory.getInstance().findEncoder(cc.getCodecId()));
cc.clearWrapperCache();
tmpFrame.setLineSize(0, cc.getFrameSize() * cc.getChannels() * AVSampleFormat.getBitsPerSample(cc.getSampleFormat()) / 8);
offset = 0;
}
private IPacketWrapper flushFrame() throws LibavException {
IPacketWrapper result = packet;
offset = 0;
result.init();
result.setData(outputBuffer);
result.setSize(outputBufferSize);
int tmp = tmpFrame.getLineSize()[0];
tmpFrame.setLineSize(0, offset);
if (!cc.encodeAudioFrame(offset == 0 ? null : tmpFrame, result))
result = null;
else
result.setStreamIndex(stream.getIndex());
tmpFrame.setLineSize(0, tmp);
return result;
}
private IPacketWrapper encodeFrame(IFrameWrapper frame) throws LibavException {
Pointer data = frame.getData()[0];
int tmp, size = frame.getLineSize()[0];
while (size > 0) {
tmp = tmpFrame.getLineSize()[0] - offset;
if (size < tmp)
tmp = size;
cLib.memcpy(tmpFrame.getData()[0].share(offset), data, tmp);
offset += tmp;
size -= tmp;
data = data.share(tmp);
if (offset == tmpFrame.getLineSize()[0]) {
offset = 0;
packet.init();
packet.setData(outputBuffer);
packet.setSize(outputBufferSize);
if (!cc.encodeAudioFrame(tmpFrame, packet))
return null;
packet.setStreamIndex(stream.getIndex());
if (frame.getPts() != IAVUtilLibrary.AV_NOPTS_VALUE) {
if (ptsOffset == -1)
ptsOffset = frame.getPts();
// FIX: this does not allow to change frame rate neither respects the codec context time base
//System.out.printf("encoding video frame: pts = %d (pts_offset = %d, source_pts = %d)\n", frame.getPts() - ptsOffset, ptsOffset, frame.getPts());
packet.setPts(ptsTransformBase.mul(frame.getPts() - ptsOffset).longValue());
packet.setDts(packet.getPts());
}
return packet;
}
}
return null;
}
private void sendPacket(IPacketWrapper packet) throws LibavException {
IPacketWrapper tmp;
synchronized (consumers) {
for (IPacketConsumer c : consumers) {
tmp = packet.clone();
c.processPacket(this, tmp);
tmp.free();
}
}
}
@Override
public void addPacketConsumer(IPacketConsumer c) {
consumers.add(c);
}
@Override
public void removePacketConsumer(IPacketConsumer c) {
consumers.remove(c);
}
}