Using lti civil for video(adding support for mac) and add h264 video support using ffmpeg-java.

cusax-fix
Damian Minkov 18 years ago
parent a34e5a9153
commit 5edc84047f

@ -907,6 +907,11 @@
<zipfileset src="${lib.win.noinst}/sound.jar" prefix=""/>
<zipfileset src="${lib.noinst}/nist-sdp-1.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/jspeex.jar" prefix=""/>
<zipfileset src="${lib.noinst}/fmj.jar" prefix=""/>
<zipfileset src="${lib.noinst}/lti-civil-no_s_w_t.jar" prefix=""/>
<zipfileset src="${lib.noinst}/ffmpeg-java-gpl.jar" prefix=""/>
<zipfileset src="${lib.noinst}/jna.jar" prefix=""/>
</jar>
<!-- Creates a bundle containing the impl of the media package and
@ -922,6 +927,11 @@
<zipfileset src="${lib.lin.noinst}/jmf.jar" prefix=""/>
<zipfileset src="${lib.noinst}/nist-sdp-1.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/jspeex.jar" prefix=""/>
<zipfileset src="${lib.noinst}/fmj.jar" prefix=""/>
<zipfileset src="${lib.noinst}/lti-civil-no_s_w_t.jar" prefix=""/>
<zipfileset src="${lib.noinst}/ffmpeg-java-gpl.jar" prefix=""/>
<zipfileset src="${lib.noinst}/jna.jar" prefix=""/>
</jar>
<!-- Creates a bundle containing the impl of the media package and
@ -937,6 +947,11 @@
<zipfileset src="${lib.mac.noinst}/jmf.jar" prefix=""/>
<zipfileset src="${lib.noinst}/nist-sdp-1.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/jspeex.jar" prefix=""/>
<zipfileset src="${lib.noinst}/fmj.jar" prefix=""/>
<zipfileset src="${lib.noinst}/lti-civil-no_s_w_t.jar" prefix=""/>
<zipfileset src="${lib.noinst}/ffmpeg-java-gpl.jar" prefix=""/>
<zipfileset src="${lib.noinst}/jna.jar" prefix=""/>
</jar>
</target>
@ -1033,6 +1048,7 @@ javax.swing.event, javax.swing.border"/>
<zipfileset src="${lib.noinst}/log4j-1.2.8.jar" prefix=""/>
</jar>
</target>
<target name="bundle-sip-slick">
<!--internal-target- Creates a bundle containing the all tests for the
sip implementation of the protocol provider package -->
@ -1343,7 +1359,7 @@ javax.swing.event, javax.swing.border"/>
prefix="net/java/sip/communicator/plugin/sipaccregwizz"/>
</jar>
</target>
<!-- BUNDLE-PLUGIN-GIBBERISHACCREGWIZZ -->
<target name="bundle-plugin-gibberishaccregwizz">
<!-- Creates a bundle for the plugin Gibberish Account Registration

@ -34,6 +34,10 @@
<classpathentry kind="lib" path="lib/installer-exclude/Stun4J.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/transparency.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/ymsg_network_v0_63.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/ffmpeg-java-gpl.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/fmj.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/jna.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/lti-civil-no_s_w_t.jar"/>
<classpathentry kind="lib" path="lib/os-specific/linux/installer-exclude/jmf.jar"/>
<classpathentry kind="lib" path="lib/os-specific/linux/jdic_stub.jar"/>
<classpathentry kind="lib" path="lib/os-specific/mac/installer-exclude/jmf.jar"/>

@ -38,6 +38,11 @@ org.osgi.framework.system.packages= org.osgi.framework; ; version=1.3.0, \
com.apple.eio; \
com.apple.cocoa.application; \
com.apple.cocoa.foundation; \
quicktime; \
quicktime.std.sg; \
quicktime.qd; \
quicktime.util; \
quicktime.std.image; \
org.xml.sax.helpers; \
javax.crypto; \
javax.crypto.spec; \

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -235,7 +235,7 @@ public class CallSessionImpl
8,
1,
-1,
AudioFormat.SIGNED)
AudioFormat.SIGNED)
};
/**
@ -256,6 +256,7 @@ public static enum ZRTPCustomInfoCodes
* to be done for every time we instantiate an RTP manager.
*/
private static boolean formatsRegisteredOnce = false;
private static boolean formatsRegisteredOnceVideo = false;
/**
* The last <code>intendedDestination</code> to which
@ -283,7 +284,7 @@ public CallSessionImpl(Call call,
registerCustomCodecFormats(audioRtpManager);
// not currently needed, we don't have any custom video formats.
// registerCustomCodecFormats(videoRtpManager);
registerCustomVideoCodecFormats(videoRtpManager);
call.addCallChangeListener(this);
initializePortNumbers();
@ -1599,7 +1600,7 @@ private Vector createMediaDescriptions(
{
am.setAttribute("rtpmap", "4 G723/8000");
am.setAttribute("fmtp", "4 annexa=no;bitrate=6.3");
}
}
}
byte onHold = this.onHold;
@ -1624,6 +1625,19 @@ private Vector createMediaDescriptions(
, 1
, "RTP/AVP"
, supportedVideoEncodings);
String h264Str = String.valueOf(Constants.H264_RTP_SDP);
for (int i = 0; i < supportedVideoEncodings.length; i++)
{
if(supportedVideoEncodings[i].equals(h264Str))
{
vm.setAttribute("rtpmap",
Constants.H264_RTP_SDP + " H264/90000");
vm.setAttribute("fmtp",
Constants.H264_RTP_SDP + " packetization-mode=1");
}
}
byte onHold = this.onHold;
if (!mediaServCallback.getDeviceConfiguration()
@ -2224,6 +2238,30 @@ static void registerCustomCodecFormats(RTPManager rtpManager)
formatsRegisteredOnce = true;
}
static void registerCustomVideoCodecFormats(RTPManager rtpManager)
{
// if we have already registered custom formats and we are running JMF
// we bail out.
if (!FMJConditionals.REGISTER_FORMATS_WITH_EVERY_RTP_MANAGER
&& formatsRegisteredOnceVideo)
{
return;
}
javax.media.Format format = new VideoFormat(Constants.H264_RTP);
logger.debug("registering format " + format + " with RTP manager");
/*
* NOTE (mkoch@rowa.de): com.sun.media.rtp.RtpSessionMgr.addFormat
* leaks memory, since it stores the Format in a static Vector.
* AFAIK there is no easy way around it, but the memory impact
* should not be too bad.
*/
rtpManager.addFormat(
format, MediaUtils.jmfToSdpEncoding(format.getEncoding()));
formatsRegisteredOnceVideo = true;
}
/**
* Indicates that a change has occurred in the state of the source call.

@ -16,6 +16,7 @@
import javax.media.protocol.*;
import javax.sdp.*;
import net.java.sip.communicator.impl.media.codec.*;
import net.java.sip.communicator.impl.media.device.*;
import net.java.sip.communicator.service.configuration.*;
import net.java.sip.communicator.service.media.MediaException;
@ -56,6 +57,7 @@ public class MediaControl
*/
private String[] supportedVideoEncodings = new String[]
{
Integer.toString(Constants.H264_RTP_SDP),
// javax.media.format.VideoFormat.H263_RTP
Integer.toString(SdpConstants.H263),
// javax.media.format.VideoFormat.JPEG_RTP
@ -140,6 +142,7 @@ public class MediaControl
*/
private static String[] customCodecs = new String[]
{
FMJConditionals.FMJ_CODECS
? "net.sf.fmj.media.codec.audio.alaw.Encoder"
: "net.java.sip.communicator.impl.media.codec.audio.alaw.JavaEncoder",
@ -152,6 +155,9 @@ public class MediaControl
FMJConditionals.FMJ_CODECS
? "net.sf.fmj.media.codec.audio.ulaw.Packetizer"
: "net.java.sip.communicator.impl.media.codec.audio.ulaw.Packetizer",
"net.java.sip.communicator.impl.media.codec.video.h264.NativeEncoder",
"net.java.sip.communicator.impl.media.codec.video.h264.NativeDecoder",
"net.java.sip.communicator.impl.media.codec.video.ImageScaler",
"net.java.sip.communicator.impl.media.codec.audio.speex.JavaEncoder",
"net.java.sip.communicator.impl.media.codec.audio.speex.JavaDecoder",
"net.java.sip.communicator.impl.media.codec.audio.ilbc.JavaEncoder",
@ -167,7 +173,8 @@ public class MediaControl
*/
private static String[] customPackages = new String[]
{ // datasource for low latency ALSA input
"net.java.sip.communicator.impl"
"net.java.sip.communicator.impl",
"net.sf.fmj"
};
/**
@ -230,6 +237,7 @@ private void initializeFormatPreferences()
{
//first init default preferences
//video
setEncodingPreference(Constants.H264_RTP, 1100);
setEncodingPreference(SdpConstants.H263, 1000);
setEncodingPreference(SdpConstants.JPEG, 950);
setEncodingPreference(SdpConstants.H261, 800);
@ -628,6 +636,7 @@ private void calculateSupportedEncodings()
}
else if (format instanceof VideoFormat)
{
System.out.println("format " + format + "/" + encoding + "/" + sdp);
if (!transmittableVideoEncodings.contains(sdp))
{
if (logger.isDebugEnabled())
@ -1188,10 +1197,24 @@ private void registerCustomCodecs()
catch (Throwable ex)
{
logger.debug("Codec : " + className +
" is NOT succsefully registered");
" is NOT succsefully registered", ex);
}
}
}
String className = "com.ibm.media.codec.video.h263.JavaDecoder";
try
{
boolean result =
PlugInManager.removePlugIn(className, PlugInManager.CODEC);
logger.debug("Codec : " + className +
" is succsefully unregistered : " + result);
}
catch (Throwable ex)
{
logger.debug("Codec : " + className +
" is NOT succsefully registered");
}
try
{

@ -92,6 +92,9 @@ public static String sdpToJmfEncoding(String sdpEncodingStr) {
case SdpConstants.H261 :
return VideoFormat.H261_RTP;
case Constants.H264_RTP_SDP :
return Constants.H264_RTP;
default :
return null;
@ -137,6 +140,8 @@ public static int jmfToSdpEncoding(String jmfEncoding) {
return SdpConstants.JPEG;
} else if (jmfEncoding.equals(VideoFormat.H261_RTP)) {
return SdpConstants.H261;
} else if (jmfEncoding.equals(Constants.H264_RTP)) {
return Constants.H264_RTP_SDP;
} else if (jmfEncoding.equals(Constants.ILBC)) {
return 97;
} else if (jmfEncoding.equals(Constants.ILBC_RTP)) {

@ -17,6 +17,9 @@ public class Constants
public static final String SPEEX = "speex";
public static final String ILBC_RTP = "ilbc/rtp";
public static final String ILBC = "ilbc";
public static final String H264_RTP = "h264/rtp";
public static final int H264_RTP_SDP = 99;
/**
* mode : Frame size for the encoding/decoding

@ -0,0 +1,122 @@
package net.java.sip.communicator.impl.media.codec.video;
import java.awt.Dimension;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import javax.media.Buffer;
import javax.media.Codec;
import javax.media.Format;
import javax.media.format.RGBFormat;
import javax.media.format.VideoFormat;
import net.sf.fmj.media.AbstractCodec;
import net.sf.fmj.media.util.BufferToImage;
import net.sf.fmj.media.util.ImageToBuffer;
/**
* Codec that scales images from one size to another.
* Interestingly, cross-platform JMF does not appear to have a corresponding codec.
* @author Ken Larson
*
* Original from fmj project, changed only output format sizes.
* The sizes are those supported in h263 and h264.
* @author Damian Minkov
*/
public class ImageScaler extends AbstractCodec implements Codec
{
// TODO: all formats supported by BufferToImage
private final Format[] supportedInputFormats = new Format[] {
new RGBFormat(null, -1, Format.intArray, -1.0f, 32, -1, -1, -1),
};
private final Format[] supportedOutputFormats = new Format[] {
//P720
new RGBFormat(new Dimension(720, 480), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
//CIF4
new RGBFormat(new Dimension(704, 576), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
//CIF
new RGBFormat(new Dimension(352, 288), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
//QCIF
new RGBFormat(new Dimension(176, 144), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
//SQCIF
new RGBFormat(new Dimension(128, 96), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
};
@Override
public Format[] getSupportedInputFormats()
{
return supportedInputFormats;
}
@Override
public Format[] getSupportedOutputFormats(Format input)
{
// if (input == null)
return supportedOutputFormats;
// VideoFormat inputCast = (VideoFormat) input;
// final Format[] result = new Format[] {
// new RGBFormat(DIMENSION, -1, Format.intArray, -1.0f, 32, -1, -1, -1)};
// // TODO: we have to specify the RGB, etc. in the output format.
// return result;
}
private BufferToImage bufferToImage;
@Override
public Format setInputFormat(Format format)
{
final VideoFormat videoFormat = (VideoFormat) format;
if (videoFormat.getSize() == null)
return null; // must set a size.
// logger.fine("FORMAT: " + MediaCGUtils.formatToStr(format));
// TODO: check VideoFormat and compatibility
bufferToImage = new BufferToImage((VideoFormat) format);
return super.setInputFormat(format);
}
@Override
public int process(Buffer input, Buffer output)
{
if (!checkInputBuffer(input))
{
return BUFFER_PROCESSED_FAILED;
}
if (isEOM(input))
{
propagateEOM(output); // TODO: what about data? can there be any?
return BUFFER_PROCESSED_OK;
}
final BufferedImage image = (BufferedImage) bufferToImage.createImage(input);
final Dimension inputSize = ((VideoFormat) inputFormat).getSize();
final Dimension outputSize = ((VideoFormat) outputFormat).getSize();
final double scaleX = ((double) outputSize.width) / ((double) inputSize.width);
final double scaleY = ((double) outputSize.height) / ((double) inputSize.height);
final BufferedImage scaled = scale(image, scaleX, scaleY); // TODO: is the size exact? what about rounding errors?
// System.out.println("scaled: " + scaled.getWidth() + "x" + scaled.getHeight());
final Buffer b = ImageToBuffer.createBuffer(scaled, ((VideoFormat) outputFormat).getFrameRate());
output.setData(b.getData());
output.setLength(b.getLength());
output.setOffset(b.getOffset());
output.setFormat(b.getFormat());
// TODO: what about format?
return BUFFER_PROCESSED_OK;
}
private BufferedImage scale(BufferedImage bi, double scaleX, double scaleY)
{
AffineTransform tx = new AffineTransform();
tx.scale(scaleX, scaleY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
return op.filter(bi, null);
}
}

@ -0,0 +1,158 @@
package net.java.sip.communicator.impl.media.codec.video.h264;
import javax.media.*;
import net.java.sip.communicator.util.*;
/**
* Parses H264 rtp headers and extracts the data in the format
* the decoder expects it. RFC3984.
*
* @author Damian Minkov
*/
public class H264Parser
{
private final Logger logger = Logger.getLogger(H264Parser.class);
// allocate enough space for the incoming data
private static final int MAX_FRAME_SIZE = 128 * 1024;
// every NAL starts with this start sequence
private static final byte[] startSequence = { 0, 0, 1};
// the timestamp of the last received rtp packet
private long lastTimestamp = -1;
// the result data is collected in this buffer
private byte[] encodedFrame = new byte[MAX_FRAME_SIZE];
// the size of the result data
private int encodedFrameLen;
/**
* New rtp packet is received. We push it to the parser to extract the data.
* @param inputBuffer the data from the rtp packet
* @return true if the result data must be passed to the decoder.
*/
public boolean pushRTPInput(Buffer inputBuffer)
{
long currentStamp = inputBuffer.getTimeStamp();
// the rtp marker field points that this is the last packet of
// the received frame
boolean hasMarker =
(inputBuffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0;
// if the timestamp changes we are starting receiving a new frame
if(!(currentStamp == lastTimestamp))
{
encodedFrame = new byte[MAX_FRAME_SIZE];
encodedFrameLen = 0;
}
// the new frame timestamp
lastTimestamp = currentStamp;
byte[] inData = (byte[]) inputBuffer.getData();
int inputOffset = inputBuffer.getOffset();
byte fByte = inData[inputOffset];
int type = fByte & 0x1f;
// types from 1 to 23 are treated the same way
if (type >= 1 && type <= 23)
{
System.arraycopy(startSequence, 0, encodedFrame, encodedFrameLen, startSequence.length);
encodedFrameLen += startSequence.length;
int len = inputBuffer.getLength();
System.arraycopy(inData, inputOffset, encodedFrame, encodedFrameLen, len);
encodedFrameLen += len;
}
else if (type == 24)
{
//return deencapsulateSTAP(inputBuffer);
}
else if (type == 28)
{
deencapsulateFU(fByte, inputBuffer);
}
else
{
logger.warn("Skipping unsupported NAL unit type");
return false;
}
if(hasMarker)
{
return true;
}
else
{
return false;
}
}
/**
* Extract data from FU packet. This are packets accross several
* rtp packets, the fisrt has a start bit set, we store all data
* and don't care about end bit.
* @param nal
* @param inputBuffer
*/
private void deencapsulateFU (byte nal, Buffer inputBuffer)
{
byte[] buf = (byte[])inputBuffer.getData();
int len = inputBuffer.getLength();
int offset = inputBuffer.getOffset();
offset++;
len--;
byte fu_indicator = nal;
byte fu_header = buf[offset];
boolean start_bit = (fu_header >> 7) != 0;
//boolean end_bit = ((fu_header & 0x40) >> 6) != 0;
int nal_type = (fu_header & 0x1f);
byte reconstructed_nal;
//reconstruct this packet's true nal; only the data follows..
//the original nal forbidden bit and NRI are stored in this packet's nal;
reconstructed_nal = (byte)(fu_indicator & (byte)0xe0);
reconstructed_nal |= nal_type;
// skip the fu_header...
offset++;
len--;
if(start_bit)
{
// copy in the start sequence, and the reconstructed nal....
System.arraycopy(startSequence, 0,
encodedFrame, encodedFrameLen, startSequence.length);
encodedFrameLen += startSequence.length;
encodedFrame[encodedFrameLen] = reconstructed_nal;
encodedFrameLen++;
System.arraycopy(buf, offset, encodedFrame, encodedFrameLen, len);
encodedFrameLen += len;
}
else
{
System.arraycopy(buf, offset, encodedFrame, encodedFrameLen, len);
encodedFrameLen += len;
}
}
/**
* Returns the result data extracted from one ore more rtp packest.
* @return the result data.
*/
public byte[] getEncodedFrame()
{
return encodedFrame;
}
/**
* Returns the result data length.
* @return the result length.
*/
public int getEncodedFrameLen()
{
return encodedFrameLen;
}
}

@ -0,0 +1,271 @@
package net.java.sip.communicator.impl.media.codec.video.h264;
import java.awt.*;
import javax.media.*;
import javax.media.format.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.impl.media.codec.*;
import net.sf.ffmpeg_java.*;
import net.sf.ffmpeg_java.AVCodecLibrary.*;
import com.ibm.media.codec.video.*;
import com.sun.jna.*;
import com.sun.jna.ptr.*;
/**
* Decodes incoming rtp data of type h264 and returns the result
* frames in RGB format.
*
* @author Damian Minkov
*/
public class NativeDecoder
extends VideoCodec
{
private final Logger logger = Logger.getLogger(NativeDecoder.class);
// Instances for the ffmpeg lib
private AVFormatLibrary AVFORMAT;
private AVCodecLibrary AVCODEC;
private AVUtilLibrary AVUTIL;
// The codec we will use
private AVCodec avcodec;
private AVCodecContext avcontext;
// The decoded data is stored in avpicture in native ffmpeg format (YUV)
private AVFrame avpicture;
// Used to convert decoded data to RGB
private AVFrame frameRGB;
// The parser used to parse rtp content
private H264Parser parser = new H264Parser();
// supported sizes by the codec
private Dimension[] supportedSizes = new Dimension[]
{
//P720
new Dimension(720, 480),
//CIF4
new Dimension(704, 576),
//CIF
new Dimension(352, 288),
//QCIF
new Dimension(176, 144),
//SQCIF
new Dimension(128, 96)
};
/**
* Constructs new h264 decoder
*/
public NativeDecoder()
{
supportedInputFormats =
new VideoFormat[]
{
new VideoFormat(Constants.H264_RTP)
};
defaultOutputFormats = new VideoFormat[]
{
new RGBFormat()
};
PLUGIN_NAME = "H.264 Decoder";
AVFORMAT = AVFormatLibrary.INSTANCE;
AVCODEC = AVCodecLibrary.INSTANCE;
AVUTIL = AVUtilLibrary.INSTANCE;
AVFORMAT.av_register_all();
AVCODEC.avcodec_init();
}
protected Format[] getMatchingOutputFormats(Format in)
{
VideoFormat ivf = (VideoFormat) in;
supportedOutputFormats = new VideoFormat[supportedSizes.length];
for (int i = 0; i < supportedSizes.length; i++)
{
Dimension size = supportedSizes[i];
supportedOutputFormats[i] =
//PIX_FMT_RGB32
new RGBFormat(
size,
-1,
Format.intArray,
ivf.getFrameRate(),
32,
0xFF0000,
0xFF00,
0xFF,
1,
size.width,
Format.FALSE,
Format.NOT_SPECIFIED);
}
return supportedOutputFormats;
}
/**
* Set the data input format.
*
* @return false if the format is not supported.
*/
public Format setInputFormat(Format format)
{
if (super.setInputFormat(format) != null)
{
reset();
return format;
}
else
return null;
}
/**
* Init the codec instances.
*/
public void open() throws ResourceUnavailableException
{
avcodec = AVCODEC.avcodec_find_decoder(AVCodecLibrary.CODEC_ID_H264);
avcontext = AVCODEC.avcodec_alloc_context();
if (AVCODEC.avcodec_open(avcontext, avcodec) < 0)
throw new RuntimeException("Could not open codec ");
avpicture = AVCODEC.avcodec_alloc_frame();
avcontext.lowres = 1;
avcontext.workaround_bugs = 1;
AVUTIL.av_log_set_callback(new AVUtilLibrary.LogCallback()
{
public void callback(Pointer p, int l, String fmtS, Pointer va_list)
{
logger.info("decoder: " + fmtS);
}
});
frameRGB = AVCODEC.avcodec_alloc_frame();
opened = true;
}
public void close()
{
if (opened)
{
opened = false;
super.close();
AVCODEC.avcodec_close(avcontext);
AVUTIL.av_free(avpicture.getPointer());
AVUTIL.av_free(avcontext.getPointer());
}
}
public int process(Buffer inputBuffer, Buffer outputBuffer)
{
if (!checkInputBuffer(inputBuffer))
{
return BUFFER_PROCESSED_FAILED;
}
if (isEOM(inputBuffer) || !opened)
{
propagateEOM(outputBuffer);
return BUFFER_PROCESSED_OK;
}
if(!parser.pushRTPInput(inputBuffer))
{
return OUTPUT_BUFFER_NOT_FILLED;
}
// fills the incoming data
IntByReference got_picture = new IntByReference();
Pointer encBuf = AVUTIL.av_malloc(parser.getEncodedFrameLen());
arraycopy(parser.getEncodedFrame(), 0, encBuf, 0, parser.getEncodedFrameLen());
// decodes the data
AVCODEC.avcodec_decode_video(
avcontext, avpicture, got_picture, encBuf, parser.getEncodedFrameLen());
if(got_picture.getValue() == 0)
{
outputBuffer.setDiscard(true);
AVUTIL.av_free(encBuf);
return BUFFER_PROCESSED_OK;
}
// convert the picture in RGB Format
int numBytes = AVCODEC.avpicture_get_size(
AVCodecLibrary.PIX_FMT_RGB32, avcontext.width, avcontext.height);
Pointer buffer = AVUTIL.av_malloc(numBytes);
AVCODEC.avpicture_fill(
frameRGB, buffer, AVCodecLibrary.PIX_FMT_RGB32, avcontext.width, avcontext.height);
// Convert the image from its native format to RGB
AVCODEC.img_convert(frameRGB, AVCodecLibrary.PIX_FMT_RGB32,
avpicture, avcontext.pix_fmt, avcontext.width,
avcontext.height);
int[] data =
frameRGB.data0.getIntArray(0, avcontext.height * avcontext.width);
int[] outData = validateIntArraySize(outputBuffer, data.length);
System.arraycopy(data, 0, outData, 0, data.length);
outputBuffer.setOffset(0);
outputBuffer.setLength(outData.length);
outputBuffer.setData(outData);
AVUTIL.av_free(encBuf);
AVUTIL.av_free(buffer);
return BUFFER_PROCESSED_OK;
}
/**
* Fills the given src array to the pointer
* @param src the source
* @param srcPos source offset
* @param dest destination
* @param destPos destination offset
* @param length data length to be copied
*/
private void arraycopy(byte[] src, int srcPos, Pointer dest, int destPos,
int length)
{
int count = 0;
while (count < length)
{
dest.setByte(destPos++, src[srcPos++]);
count++;
}
}
public boolean checkFormat(Format format)
{
if ((format.getEncoding()).equals(Constants.H264_RTP))
{
return true;
}
else
{
return super.checkFormat(format);
}
}
}

@ -0,0 +1,447 @@
package net.java.sip.communicator.impl.media.codec.video.h264;
import java.awt.*;
import java.util.*;
import javax.media.*;
import javax.media.format.*;
import net.java.sip.communicator.impl.media.codec.*;
import net.java.sip.communicator.util.*;
import net.sf.ffmpeg_java.*;
import net.sf.ffmpeg_java.AVCodecLibrary.*;
import com.sun.media.*;
import com.sun.jna.*;
/**
* Encodes supplied data and encapsulate it in rtp according
* rfc RFC3984.
* @author Damian Minkov
*/
public class NativeEncoder
extends BasicCodec
{
private final Logger logger = Logger.getLogger(NativeEncoder.class);
// supported formats
// private final static int P720_WIDTH = 720;
// private final static int P720_HEIGHT = 480;
// private final static int CIF4_WIDTH = 704;
// private final static int CIF4_HEIGHT = 576;
private final static int CIF_WIDTH = 352;
private final static int CIF_HEIGHT = 288;
// private final static int QCIF_WIDTH = 176;
// private final static int QCIF_HEIGHT = 144;
// private final static int SQCIF_WIDTH = 128;
// private final static int SQCIF_HEIGHT = 96;
private final static String PLUGIN_NAME = "H264 Encoder";
private final static int DEF_WIDTH = CIF_WIDTH;
private final static int DEF_HEIGHT = CIF_HEIGHT;
// without the headers
private final static int MAX_PAYLOAD_SIZE = 1400;
private final static int INPUT_BUFFER_PADDING_SIZE = 8;
private final static Format[] defOutputFormats =
{
new VideoFormat(Constants.H264_RTP)
};
// the frame rate we will use
private final static int TARGET_FRAME_RATE = 15;
// Instances for the ffmpeg lib
private AVFormatLibrary AVFORMAT;
private AVCodecLibrary AVCODEC;
private AVUtilLibrary AVUTIL;
// The codec we will use
private AVCodec avcodec;
private AVCodecContext avcontext;
// the encoded data is stored in avpicture
private AVFrame avpicture;
// we use this buffer to supply data to encoder
private Pointer encFrameBuffer;
// the supplied data length
private int encFrameLen;
private Pointer rawFrameBuffer;
// Vector to store NALs , when packaging encoded frame to rtp packets
private Vector<byte[]> nals = new Vector<byte[]>();
// last timestamp we processed
private long lastTimeStamp = 0;
// the current rtp sequence
private int seq = 0;
/**
* Constructor
*/
public NativeEncoder()
{
int strideY = DEF_WIDTH;
int strideUV = strideY / 2;
int offsetU = strideY * DEF_HEIGHT;
int offsetV = offsetU + strideUV * DEF_HEIGHT / 2;
int inputYuvLength = (strideY + strideUV) * DEF_HEIGHT;
float sourceFrameRate = TARGET_FRAME_RATE;
inputFormats = new Format[]{
new YUVFormat(new Dimension(DEF_WIDTH, DEF_HEIGHT),
inputYuvLength + INPUT_BUFFER_PADDING_SIZE,
Format.byteArray, sourceFrameRate, YUVFormat.YUV_420, strideY,
strideUV, 0, offsetU, offsetV)
};
inputFormat = null;
outputFormat = null;
AVFORMAT = AVFormatLibrary.INSTANCE;
AVCODEC = AVCodecLibrary.INSTANCE;
AVUTIL = AVUtilLibrary.INSTANCE;
AVFORMAT.av_register_all();
AVCODEC.avcodec_init();
}
private Format[] getMatchingOutputFormats(Format in)
{
VideoFormat videoIn = (VideoFormat) in;
Dimension inSize = videoIn.getSize();
outputFormats =
new VideoFormat[]
{
new VideoFormat(Constants.H264_RTP, inSize,
Format.NOT_SPECIFIED, Format.byteArray, videoIn
.getFrameRate()) };
return outputFormats;
}
/**
* Return the list of formats supported at the output.
*/
public Format[] getSupportedOutputFormats(Format in)
{
// null input format
if (in == null)
return defOutputFormats;
// mismatch input format
if (!(in instanceof VideoFormat) || null == matches(in, inputFormats))
return new Format[0];
return getMatchingOutputFormats(in);
}
public Format setInputFormat(Format in)
{
// mismatch input format
if (!(in instanceof VideoFormat) || null == matches(in, inputFormats))
return null;
VideoFormat videoIn = (VideoFormat) in;
Dimension inSize = videoIn.getSize();
if (inSize == null)
inSize = new Dimension(DEF_WIDTH, DEF_HEIGHT);
YUVFormat yuv = (YUVFormat) videoIn;
if (yuv.getOffsetU() > yuv.getOffsetV())
return null;
int strideY = inSize.width;
int strideUV = strideY / 2;
int offsetU = strideY * inSize.height;
int offsetV = offsetU + strideUV * inSize.height / 2;
int inputYuvLength = (strideY + strideUV) * inSize.height;
float sourceFrameRate = videoIn.getFrameRate();
inputFormat =
new YUVFormat(inSize, inputYuvLength + INPUT_BUFFER_PADDING_SIZE,
Format.byteArray, sourceFrameRate, YUVFormat.YUV_420, strideY,
strideUV, 0, offsetU, offsetV);
// Return the selected inputFormat
return inputFormat;
}
public Format setOutputFormat(Format out)
{
// mismatch output format
if (!(out instanceof VideoFormat)
|| null == matches(out, getMatchingOutputFormats(inputFormat)))
return null;
VideoFormat videoOut = (VideoFormat) out;
Dimension outSize = videoOut.getSize();
if (outSize == null)
{
Dimension inSize = ((VideoFormat) inputFormat).getSize();
if (inSize == null)
outSize = new Dimension(DEF_WIDTH, DEF_HEIGHT);
else
outSize = inSize;
}
outputFormat =
new VideoFormat(videoOut.getEncoding(),
outSize, outSize.width * outSize.height,
Format.byteArray, videoOut.getFrameRate());
// Return the selected outputFormat
return outputFormat;
}
public int process(Buffer inBuffer, Buffer outBuffer)
{
// if there are some nals we check and send them
if(nals.size() > 0)
{
byte[] buf = nals.remove(0);
//int type = buf[0] & 0x1f;
// if nal is too big we must make FU packet
if(buf.length > MAX_PAYLOAD_SIZE)
{
int bufOfset = 0;
int size = buf.length;
int nri = buf[bufOfset] & 0x60;
byte[] tmp = new byte[MAX_PAYLOAD_SIZE];
tmp[0] = 28; /* FU Indicator; Type = 28 ---> FU-A */
tmp[0] |= nri;
tmp[1] = buf[bufOfset];
tmp[1] |= 1 << 7;
bufOfset += 1;
size -= 1;
int currentSIx = 0;
while (size + 2 > MAX_PAYLOAD_SIZE)
{
System.arraycopy(buf, bufOfset, tmp, 2, MAX_PAYLOAD_SIZE - 2);
nals.add(currentSIx++, tmp.clone());
bufOfset += MAX_PAYLOAD_SIZE - 2;
size -= MAX_PAYLOAD_SIZE - 2;
tmp[1] &= ~(1 << 7);
}
byte[] tmp2 = new byte[size + 2];
tmp2[0] = tmp[0];
tmp2[1] = tmp[1];
tmp2[1] |= 1 << 6;
System.arraycopy(buf, bufOfset, tmp2, 2, size);
nals.add(currentSIx++, tmp2);
return INPUT_BUFFER_NOT_CONSUMED | OUTPUT_BUFFER_NOT_FILLED;
}
// size is ok lets send it
outBuffer.setData(buf);
outBuffer.setLength(buf.length);
outBuffer.setOffset(0);
outBuffer.setTimeStamp(lastTimeStamp);
outBuffer.setSequenceNumber(seq++);
// if more nals exist process them
if(nals.size() > 0)
{
return BUFFER_PROCESSED_OK | INPUT_BUFFER_NOT_CONSUMED;
}
else
{
// its the last one from current frame, mark it
outBuffer.setFlags(outBuffer.getFlags() | Buffer.FLAG_RTP_MARKER);
return BUFFER_PROCESSED_OK;
}
}
if (isEOM(inBuffer))
{
propagateEOM(outBuffer);
reset();
return BUFFER_PROCESSED_OK;
}
if (inBuffer.isDiscard())
{
outBuffer.setDiscard(true);
reset();
return BUFFER_PROCESSED_OK;
}
Format inFormat = inBuffer.getFormat();
if (inFormat != inputFormat && !(inFormat.matches(inputFormat)))
{
setInputFormat(inFormat);
}
if (inBuffer.getLength() < 10)
{
outBuffer.setDiscard(true);
reset();
return BUFFER_PROCESSED_OK;
}
// copy data to avpicture
rawFrameBuffer.write(0,
(byte[])inBuffer.getData(), inBuffer.getOffset(), encFrameLen);
// encode data
int encLen =
AVCODEC.avcodec_encode_video(
avcontext, encFrameBuffer, encFrameLen, avpicture);
byte[] r = encFrameBuffer.getByteArray(0, encLen);
// split encoded data to nals
// we know it starts with 00 00 01
int ix = 3;
int prevIx = 1;
while((ix = ff_avc_find_startcode(r, ix, r.length)) < r.length)
{
int len = ix - prevIx;
byte[] b = new byte[len -4];
System.arraycopy(r, prevIx+ 3, b, 0, len-4);
nals.add(b);
prevIx = ix;
ix = prevIx + 3;
}
int len = ix - prevIx;
byte[] b = new byte[len - 3];
System.arraycopy(r, prevIx+ 3, b, 0, len-3);
nals.add(b);
lastTimeStamp = inBuffer.getTimeStamp();
return INPUT_BUFFER_NOT_CONSUMED | OUTPUT_BUFFER_NOT_FILLED;
}
/**
* Find a NAL in the encoded data.
* @param buff the encoded data
* @param startIx starting index
* @param endIx end index
* @return starting position of the NAL
*/
private int ff_avc_find_startcode(byte[] buff, int startIx, int endIx)
{
while(startIx < (endIx - 3))
{
if(buff[startIx] == 0 &&
buff[startIx + 1] == 0 &&
buff[startIx + 2] == 1)
return startIx;
startIx++;
}
return endIx;
}
public synchronized void open() throws ResourceUnavailableException
{
if (!opened)
{
super.open();
if (inputFormat == null)
throw new ResourceUnavailableException(
"No input format selected");
if (outputFormat == null)
throw new ResourceUnavailableException(
"No output format selected");
AVCODEC.avcodec_init();
avcodec = AVCODEC.avcodec_find_encoder(AVCodecLibrary.CODEC_ID_H264);
avcontext = AVCODEC.avcodec_alloc_context();
avpicture = AVCODEC.avcodec_alloc_frame();
avcontext.pix_fmt = AVFormatLibrary.PIX_FMT_YUV420P;
avcontext.width = DEF_WIDTH;
avcontext.height = DEF_HEIGHT;
avcontext.time_base = new FFMPEGLibrary.AVRational(1, TARGET_FRAME_RATE);
avpicture.linesize[0] = DEF_WIDTH;
avpicture.linesize[1] = DEF_WIDTH / 2;
avpicture.linesize[2] = DEF_WIDTH / 2;
//avpicture.quality = (int)10;
avcontext.qcompress = 1;
//int _bitRate = 267000;
//avcontext.bit_rate = (_bitRate * 3) >> 2; // average bit rate
AVUTIL.av_log_set_callback(new AVUtilLibrary.LogCallback(){
public void callback(Pointer p, int l, String fmtS,
Pointer va_list)
{
logger.info("encoder: " + fmtS);
}});
if (AVCODEC.avcodec_open(avcontext, avcodec) < 0)
throw new RuntimeException("Could not open codec ");
opened = true;
encFrameLen = (DEF_WIDTH * DEF_HEIGHT * 3) / 2;
rawFrameBuffer = AVUTIL.av_malloc(encFrameLen);
encFrameBuffer = AVUTIL.av_malloc(encFrameLen);
int size = DEF_WIDTH * DEF_HEIGHT;
avpicture.data0 = rawFrameBuffer;
avpicture.data1 = avpicture.data0.share(size);
avpicture.data2 = avpicture.data1.share(size/4);
}
}
public synchronized void close()
{
if (opened)
{
super.close();
AVCODEC.avcodec_close(avcontext);
AVUTIL.av_free(avpicture.getPointer());
AVUTIL.av_free(avcontext.getPointer());
AVUTIL.av_free(rawFrameBuffer);
AVUTIL.av_free(encFrameBuffer);
}
}
public String getName()
{
return PLUGIN_NAME;
}
public java.lang.Object[] getControls()
{
if (controls == null)
{
controls =
new Control[0];
}
return (Object[]) controls;
}
}

@ -12,6 +12,7 @@
import java.io.*;
import java.util.*;
import javax.media.*;
import javax.media.format.*;
@ -174,7 +175,7 @@ private void detectCaptureDevices()
//those that do not apply will silently fail.
logger.info("Looking for video capture devices");
int nDevices = 0;
//Windows
/* //Windows
try
{
VFWAuto vfwAuto = new VFWAuto();
@ -231,19 +232,18 @@ private void detectCaptureDevices()
{
logger.debug("No V4l video detected: " + exc.getMessage());
}
*/
//FMJ
// try
// {
// new FMJCivilVideoAuto();
// }
// catch (Throwable exc)
// {
// logger.debug("No FMJ CIVIL video detected: " + exc.getMessage());
// }
try
{
new FMJCivilVideoAuto();
}
catch (Throwable exc)
{
logger.debug("No FMJ CIVIL video detected: " + exc.getMessage(), exc);
}
}
/**

@ -19,6 +19,11 @@ Import-Package: org.osgi.framework,
net.java.sip.communicator.service.resources,
javax.sound,
javax.sound.sampled,
quicktime,
quicktime.std.sg,
quicktime.qd,
quicktime.util,
quicktime.std.image,
javax.crypto,
javax.crypto.spec,
javax.crypto.interfaces,

Loading…
Cancel
Save