* Added new Jheora video system, however as advertised, audio will NOT work. If you want to try and fix it, go ahead.
git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9312 75d07b2b-3a1a-0410-a2c5-0572b91ccdca3.0
parent
988110e360
commit
12d3e277e6
@ -0,0 +1,179 @@ |
|||||||
|
/* |
||||||
|
* To change this template, choose Tools | Templates |
||||||
|
* and open the template in the editor. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.jme3.newvideo; |
||||||
|
|
||||||
|
import com.fluendo.jst.Buffer; |
||||||
|
import com.fluendo.jst.Caps; |
||||||
|
import com.fluendo.jst.Element; |
||||||
|
import com.fluendo.jst.ElementFactory; |
||||||
|
import com.fluendo.jst.Event; |
||||||
|
import com.fluendo.jst.Message; |
||||||
|
import com.fluendo.jst.Pad; |
||||||
|
import com.fluendo.utils.Debug; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
|
||||||
|
public class InputStreamSrc extends Element { |
||||||
|
private InputStream input; |
||||||
|
private long contentLength; |
||||||
|
private long offset = 0; |
||||||
|
private long offsetLastMessage = 0; |
||||||
|
private long skipBytes = 0; |
||||||
|
private String mime; |
||||||
|
private Caps outCaps; |
||||||
|
private boolean discont = true; |
||||||
|
private static final int DEFAULT_READSIZE = 4096; |
||||||
|
private int readSize = DEFAULT_READSIZE; |
||||||
|
|
||||||
|
private Pad srcpad = new Pad(Pad.SRC, "src") { |
||||||
|
@Override |
||||||
|
protected void taskFunc() { |
||||||
|
int ret; |
||||||
|
int toRead; |
||||||
|
long left; |
||||||
|
|
||||||
|
// Skip to the target offset if required
|
||||||
|
if (skipBytes > 0) { |
||||||
|
Debug.info("Skipping " + skipBytes + " input bytes"); |
||||||
|
try { |
||||||
|
offset += input.skip(skipBytes); |
||||||
|
} catch (IOException e) { |
||||||
|
Debug.error("input.skip error: " + e); |
||||||
|
postMessage(Message.newError(this, "File read error")); |
||||||
|
return; |
||||||
|
} |
||||||
|
skipBytes = 0; |
||||||
|
} |
||||||
|
|
||||||
|
// Calculate the read size
|
||||||
|
if (contentLength != -1) { |
||||||
|
left = contentLength - offset; |
||||||
|
} else { |
||||||
|
left = -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (left != -1 && left < readSize) { |
||||||
|
toRead = (int) left; |
||||||
|
} else { |
||||||
|
toRead = readSize; |
||||||
|
} |
||||||
|
|
||||||
|
// Perform the read
|
||||||
|
Buffer data = Buffer.create(); |
||||||
|
data.ensureSize(toRead); |
||||||
|
data.offset = 0; |
||||||
|
try { |
||||||
|
if (toRead > 0) { |
||||||
|
data.length = input.read(data.data, 0, toRead); |
||||||
|
} else { |
||||||
|
data.length = -1; |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
e.printStackTrace(); |
||||||
|
data.length = 0; |
||||||
|
} |
||||||
|
if (data.length <= 0) { |
||||||
|
/* EOS */ |
||||||
|
|
||||||
|
postMessage(Message.newBytePosition(this, offset)); |
||||||
|
offsetLastMessage = offset; |
||||||
|
|
||||||
|
try { |
||||||
|
input.close(); |
||||||
|
} catch (Exception e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
data.free(); |
||||||
|
Debug.log(Debug.INFO, this + " reached EOS"); |
||||||
|
pushEvent(Event.newEOS()); |
||||||
|
postMessage(Message.newStreamStatus(this, false, Pad.UNEXPECTED, "reached EOS")); |
||||||
|
pauseTask(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
offset += data.length; |
||||||
|
if (offsetLastMessage > offset) { |
||||||
|
offsetLastMessage = 0; |
||||||
|
} |
||||||
|
if (offset - offsetLastMessage > contentLength / 100) { |
||||||
|
postMessage(Message.newBytePosition(this, offset)); |
||||||
|
offsetLastMessage = offset; |
||||||
|
} |
||||||
|
|
||||||
|
// Negotiate capabilities
|
||||||
|
if (srcpad.getCaps() == null) { |
||||||
|
String typeMime; |
||||||
|
|
||||||
|
typeMime = ElementFactory.typeFindMime(data.data, data.offset, data.length); |
||||||
|
Debug.log(Debug.INFO, "using typefind contentType: " + typeMime); |
||||||
|
mime = typeMime; |
||||||
|
|
||||||
|
outCaps = new Caps(mime); |
||||||
|
srcpad.setCaps(outCaps); |
||||||
|
} |
||||||
|
|
||||||
|
data.caps = outCaps; |
||||||
|
data.setFlag(com.fluendo.jst.Buffer.FLAG_DISCONT, discont); |
||||||
|
discont = false; |
||||||
|
|
||||||
|
// Push the data to the peer
|
||||||
|
if ((ret = push(data)) != OK) { |
||||||
|
if (isFlowFatal(ret) || ret == Pad.NOT_LINKED) { |
||||||
|
postMessage(Message.newError(this, "error: " + getFlowName(ret))); |
||||||
|
pushEvent(Event.newEOS()); |
||||||
|
} |
||||||
|
postMessage(Message.newStreamStatus(this, false, ret, "reason: " + getFlowName(ret))); |
||||||
|
pauseTask(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected boolean activateFunc(int mode) { |
||||||
|
switch (mode) { |
||||||
|
case MODE_NONE: |
||||||
|
postMessage(Message.newStreamStatus(this, false, Pad.WRONG_STATE, "stopping")); |
||||||
|
input = null; |
||||||
|
outCaps = null; |
||||||
|
mime = null; |
||||||
|
return stopTask(); |
||||||
|
case MODE_PUSH: |
||||||
|
contentLength = -1; |
||||||
|
// until we can determine content length from IS?
|
||||||
|
// if (contentLength != -1) {
|
||||||
|
// postMessage(Message.newDuration(this, Format.BYTES, contentLength));
|
||||||
|
// }
|
||||||
|
if (input == null) |
||||||
|
return false; |
||||||
|
|
||||||
|
postMessage(Message.newStreamStatus(this, true, Pad.OK, "activating")); |
||||||
|
return startTask("JmeVideo-Src-Stream-" + Debug.genId()); |
||||||
|
default: |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
public String getFactoryName() { |
||||||
|
return "inputstreamsrc"; |
||||||
|
} |
||||||
|
|
||||||
|
public InputStreamSrc(String name) { |
||||||
|
super(name); |
||||||
|
addPad(srcpad); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public synchronized boolean setProperty(String name, java.lang.Object value) { |
||||||
|
if (name.equals("inputstream")){ |
||||||
|
input = (InputStream) value; |
||||||
|
}else if (name.equals("readSize")) { |
||||||
|
readSize = Integer.parseInt((String) value); |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,412 @@ |
|||||||
|
package com.jme3.newvideo; |
||||||
|
|
||||||
|
import com.fluendo.jst.Caps; |
||||||
|
import com.fluendo.jst.CapsListener; |
||||||
|
import com.fluendo.jst.Clock; |
||||||
|
import com.fluendo.jst.Element; |
||||||
|
import com.fluendo.jst.ElementFactory; |
||||||
|
import com.fluendo.jst.Format; |
||||||
|
import com.fluendo.jst.Message; |
||||||
|
import com.fluendo.jst.Pad; |
||||||
|
import com.fluendo.jst.PadListener; |
||||||
|
import com.fluendo.jst.Pipeline; |
||||||
|
import com.fluendo.jst.Query; |
||||||
|
import com.fluendo.utils.Debug; |
||||||
|
import com.jme3.app.Application; |
||||||
|
import com.jme3.texture.Texture2D; |
||||||
|
import java.io.InputStream; |
||||||
|
|
||||||
|
public class JmeVideoPipeline extends Pipeline implements PadListener, CapsListener { |
||||||
|
|
||||||
|
private boolean enableAudio; |
||||||
|
private boolean enableVideo; |
||||||
|
|
||||||
|
private int bufferSize = -1; |
||||||
|
private int bufferLow = -1; |
||||||
|
private int bufferHigh = -1; |
||||||
|
|
||||||
|
private Element inputstreamsrc; |
||||||
|
private Element buffer; |
||||||
|
private Element demux; |
||||||
|
private Element videodec; |
||||||
|
private Element audiodec; |
||||||
|
private Element videosink; |
||||||
|
private Element audiosink; |
||||||
|
private Element yuv2tex; |
||||||
|
private Element v_queue, v_queue2, a_queue = null; |
||||||
|
private Pad asinkpad, ovsinkpad; |
||||||
|
private Pad apad, vpad; |
||||||
|
public boolean usingJavaX = false; |
||||||
|
|
||||||
|
public InputStream inputStream; |
||||||
|
private Application app; |
||||||
|
|
||||||
|
public JmeVideoPipeline(Application app) { |
||||||
|
super("pipeline"); |
||||||
|
|
||||||
|
enableAudio = true; |
||||||
|
enableVideo = true; |
||||||
|
this.app = app; |
||||||
|
} |
||||||
|
|
||||||
|
private void noSuchElement(String elemName) { |
||||||
|
postMessage(Message.newError(this, "no such element: " + elemName)); |
||||||
|
} |
||||||
|
|
||||||
|
public void padAdded(Pad pad) { |
||||||
|
Caps caps = pad.getCaps(); |
||||||
|
|
||||||
|
if (caps == null) { |
||||||
|
Debug.log(Debug.INFO, "pad added without caps: " + pad); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Debug.log(Debug.INFO, "pad added " + pad); |
||||||
|
String mime = caps.getMime(); |
||||||
|
|
||||||
|
if (mime.equals("audio/x-vorbis")) { |
||||||
|
if (true) |
||||||
|
return; |
||||||
|
|
||||||
|
if (a_queue != null) { |
||||||
|
Debug.log(Debug.INFO, "More than one audio stream detected, ignoring all except first one"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
a_queue = ElementFactory.makeByName("queue", "a_queue"); |
||||||
|
if (a_queue == null) { |
||||||
|
noSuchElement("queue"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// if we already have a video queue: We want smooth audio playback
|
||||||
|
// over frame completeness, so make the video queue leaky
|
||||||
|
if (v_queue != null) { |
||||||
|
v_queue.setProperty("leaky", "2"); // 2 == Queue.LEAK_DOWNSTREAM
|
||||||
|
} |
||||||
|
|
||||||
|
audiodec = ElementFactory.makeByName("vorbisdec", "audiodec"); |
||||||
|
if (audiodec == null) { |
||||||
|
noSuchElement("vorbisdec"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
a_queue.setProperty("maxBuffers", "100"); |
||||||
|
|
||||||
|
add(a_queue); |
||||||
|
add(audiodec); |
||||||
|
|
||||||
|
pad.link(a_queue.getPad("sink")); |
||||||
|
a_queue.getPad("src").link(audiodec.getPad("sink")); |
||||||
|
if (!audiodec.getPad("src").link(asinkpad)) { |
||||||
|
postMessage(Message.newError(this, "audiosink already linked")); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
apad = pad; |
||||||
|
|
||||||
|
audiodec.setState(PAUSE); |
||||||
|
a_queue.setState(PAUSE); |
||||||
|
} else if (enableVideo && mime.equals("video/x-theora")) { |
||||||
|
// Constructs a chain of the form
|
||||||
|
// oggdemux -> v_queue -> theoradec -> v_queue2 -> videosink
|
||||||
|
v_queue = ElementFactory.makeByName("queue", "v_queue"); |
||||||
|
v_queue2 = ElementFactory.makeByName("queue", "v_queue2"); |
||||||
|
yuv2tex = new YUV2Texture(app); |
||||||
|
if (v_queue == null) { |
||||||
|
noSuchElement("queue"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
videodec = ElementFactory.makeByName("theoradec", "videodec"); |
||||||
|
if (videodec == null) { |
||||||
|
noSuchElement("theoradec"); |
||||||
|
return; |
||||||
|
} |
||||||
|
add(videodec); |
||||||
|
|
||||||
|
// if we have audio: We want smooth audio playback
|
||||||
|
// over frame completeness
|
||||||
|
if (a_queue != null) { |
||||||
|
v_queue.setProperty("leaky", "2"); // 2 == Queue.LEAK_DOWNSTREAM
|
||||||
|
} |
||||||
|
|
||||||
|
v_queue.setProperty("maxBuffers", "5"); |
||||||
|
v_queue2.setProperty("maxBuffers", "5"); |
||||||
|
v_queue2.setProperty("isBuffer", Boolean.FALSE); |
||||||
|
|
||||||
|
add(v_queue); |
||||||
|
add(v_queue2); |
||||||
|
add(yuv2tex); |
||||||
|
|
||||||
|
pad.link(v_queue.getPad("sink")); |
||||||
|
v_queue.getPad("src").link(videodec.getPad("sink")); |
||||||
|
|
||||||
|
// WITH YUV2TEX
|
||||||
|
videodec.getPad("src").link(yuv2tex.getPad("sink")); |
||||||
|
yuv2tex.getPad("src").link(v_queue2.getPad("sink")); |
||||||
|
v_queue2.getPad("src").link(videosink.getPad("sink")); |
||||||
|
|
||||||
|
// WITHOUT YUV2TEX
|
||||||
|
// videodec.getPad("src").link(v_queue2.getPad("sink"));
|
||||||
|
|
||||||
|
if (!v_queue2.getPad("src").link(ovsinkpad)) { |
||||||
|
postMessage(Message.newError(this, "videosink already linked")); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
vpad = pad; |
||||||
|
|
||||||
|
videodec.setState(PAUSE); |
||||||
|
v_queue.setState(PAUSE); |
||||||
|
v_queue2.setState(PAUSE); |
||||||
|
yuv2tex.setState(PAUSE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void padRemoved(Pad pad) { |
||||||
|
pad.unlink(); |
||||||
|
if (pad == vpad) { |
||||||
|
Debug.log(Debug.INFO, "video pad removed " + pad); |
||||||
|
ovsinkpad.unlink(); |
||||||
|
vpad = null; |
||||||
|
} else if (pad == apad) { |
||||||
|
Debug.log(Debug.INFO, "audio pad removed " + pad); |
||||||
|
asinkpad.unlink(); |
||||||
|
apad = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void noMorePads() { |
||||||
|
boolean changed = false; |
||||||
|
|
||||||
|
Debug.log(Debug.INFO, "all streams detected"); |
||||||
|
|
||||||
|
if (apad == null && enableAudio) { |
||||||
|
Debug.log(Debug.INFO, "file has no audio, remove audiosink"); |
||||||
|
audiosink.setState(STOP); |
||||||
|
remove(audiosink); |
||||||
|
audiosink = null; |
||||||
|
changed = true; |
||||||
|
if (videosink != null) { |
||||||
|
// videosink.setProperty("max-lateness", Long.toString(Long.MAX_VALUE));
|
||||||
|
videosink.setProperty("max-lateness", ""+Clock.SECOND); |
||||||
|
} |
||||||
|
} |
||||||
|
if (vpad == null && enableVideo) { |
||||||
|
Debug.log(Debug.INFO, "file has no video, remove videosink"); |
||||||
|
videosink.setState(STOP); |
||||||
|
|
||||||
|
remove(videosink); |
||||||
|
videosink = null; |
||||||
|
changed = true; |
||||||
|
} |
||||||
|
if (changed) { |
||||||
|
scheduleReCalcState(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Texture2D getTexture(){ |
||||||
|
if (videosink != null){ |
||||||
|
return (Texture2D) videosink.getProperty("texture"); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean buildOggPipeline() { |
||||||
|
demux = ElementFactory.makeByName("oggdemux", "OggFileDemuxer"); |
||||||
|
if (demux == null) { |
||||||
|
noSuchElement("oggdemux"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
buffer = ElementFactory.makeByName("queue", "BufferQueue"); |
||||||
|
if (buffer == null) { |
||||||
|
demux = null; |
||||||
|
noSuchElement("queue"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
buffer.setProperty("isBuffer", Boolean.TRUE); |
||||||
|
if (bufferSize != -1) { |
||||||
|
buffer.setProperty("maxSize", new Integer(bufferSize * 1024)); |
||||||
|
} |
||||||
|
if (bufferLow != -1) { |
||||||
|
buffer.setProperty("lowPercent", new Integer(bufferLow)); |
||||||
|
} |
||||||
|
if (bufferHigh != -1) { |
||||||
|
buffer.setProperty("highPercent", new Integer(bufferHigh)); |
||||||
|
} |
||||||
|
|
||||||
|
add(demux); |
||||||
|
add(buffer); |
||||||
|
|
||||||
|
// Link input stream source with bufferqueue's sink
|
||||||
|
inputstreamsrc.getPad("src").link(buffer.getPad("sink")); |
||||||
|
|
||||||
|
// Link bufferqueue's source with the oggdemuxer's sink
|
||||||
|
buffer.getPad("src").link(demux.getPad("sink")); |
||||||
|
|
||||||
|
// Receive pad events from OggDemuxer
|
||||||
|
demux.addPadListener(this); |
||||||
|
|
||||||
|
buffer.setState(PAUSE); |
||||||
|
demux.setState(PAUSE); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public void capsChanged(Caps caps) { |
||||||
|
String mime = caps.getMime(); |
||||||
|
if (mime.equals("application/ogg")) { |
||||||
|
buildOggPipeline(); |
||||||
|
} else { |
||||||
|
postMessage(Message.newError(this, "Unknown MIME type: " + mime)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean openFile() { |
||||||
|
inputstreamsrc = new InputStreamSrc("InputStreamSource"); |
||||||
|
inputstreamsrc.setProperty("inputstream", inputStream); |
||||||
|
add(inputstreamsrc); |
||||||
|
|
||||||
|
// Receive caps from InputStream source
|
||||||
|
inputstreamsrc.getPad("src").addCapsListener(this); |
||||||
|
|
||||||
|
audiosink = newAudioSink(); |
||||||
|
if (audiosink == null) { |
||||||
|
enableAudio = false; |
||||||
|
} else { |
||||||
|
asinkpad = audiosink.getPad("sink"); |
||||||
|
add(audiosink); |
||||||
|
} |
||||||
|
|
||||||
|
if (enableVideo) { |
||||||
|
videosink = new TextureVideoSink("TextureVideoSink"); |
||||||
|
videosink.setProperty("max-lateness", ""+Clock.SECOND); |
||||||
|
// Long.toString(enableAudio ? Clock.MSECOND * 20 : Long.MAX_VALUE));
|
||||||
|
add(videosink); |
||||||
|
|
||||||
|
ovsinkpad = videosink.getPad("sink"); |
||||||
|
} |
||||||
|
if (audiosink == null && videosink == null) { |
||||||
|
postMessage(Message.newError(this, "Both audio and video are disabled, can't play anything")); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
protected Element newAudioSink() { |
||||||
|
com.fluendo.plugin.AudioSink s; |
||||||
|
try { |
||||||
|
s = (com.fluendo.plugin.AudioSink) ElementFactory.makeByName("audiosinkj2", "audiosink"); |
||||||
|
Debug.log(Debug.INFO, "using high quality javax.sound backend"); |
||||||
|
} catch (Throwable e) { |
||||||
|
s = null; |
||||||
|
noSuchElement ("audiosink"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (!s.test()) { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
return s; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean cleanup() { |
||||||
|
Debug.log(Debug.INFO, "cleanup"); |
||||||
|
if (inputstreamsrc != null) { |
||||||
|
remove(inputstreamsrc); |
||||||
|
inputstreamsrc = null; |
||||||
|
} |
||||||
|
if (audiosink != null) { |
||||||
|
remove(audiosink); |
||||||
|
audiosink = null; |
||||||
|
asinkpad = null; |
||||||
|
} |
||||||
|
if (videosink != null) { |
||||||
|
remove(videosink); |
||||||
|
videosink = null; |
||||||
|
} |
||||||
|
if (buffer != null) { |
||||||
|
remove(buffer); |
||||||
|
buffer = null; |
||||||
|
} |
||||||
|
if (demux != null) { |
||||||
|
demux.removePadListener(this); |
||||||
|
remove(demux); |
||||||
|
demux = null; |
||||||
|
} |
||||||
|
if (v_queue != null) { |
||||||
|
remove(v_queue); |
||||||
|
v_queue = null; |
||||||
|
} |
||||||
|
if (v_queue2 != null) { |
||||||
|
remove(v_queue2); |
||||||
|
v_queue2 = null; |
||||||
|
} |
||||||
|
if (yuv2tex != null){ |
||||||
|
remove(yuv2tex); |
||||||
|
yuv2tex = null; |
||||||
|
} |
||||||
|
if (a_queue != null) { |
||||||
|
remove(a_queue); |
||||||
|
a_queue = null; |
||||||
|
} |
||||||
|
if (videodec != null) { |
||||||
|
remove(videodec); |
||||||
|
videodec = null; |
||||||
|
} |
||||||
|
if (audiodec != null) { |
||||||
|
remove(audiodec); |
||||||
|
audiodec = null; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected int changeState(int transition) { |
||||||
|
int res; |
||||||
|
switch (transition) { |
||||||
|
case STOP_PAUSE: |
||||||
|
if (!openFile()) { |
||||||
|
return FAILURE; |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
res = super.changeState(transition); |
||||||
|
|
||||||
|
switch (transition) { |
||||||
|
case PAUSE_STOP: |
||||||
|
cleanup(); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected boolean doSendEvent(com.fluendo.jst.Event event) { |
||||||
|
return false; // no seek support
|
||||||
|
} |
||||||
|
|
||||||
|
protected long getPosition() { |
||||||
|
Query q; |
||||||
|
long result = 0; |
||||||
|
|
||||||
|
q = Query.newPosition(Format.TIME); |
||||||
|
if (super.query(q)){ |
||||||
|
result = q.parsePositionValue(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,144 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.jme3.newvideo; |
||||||
|
|
||||||
|
import com.fluendo.jst.BusHandler; |
||||||
|
import com.fluendo.jst.Message; |
||||||
|
import com.fluendo.jst.Pipeline; |
||||||
|
import com.fluendo.utils.Debug; |
||||||
|
import com.jme3.app.SimpleApplication; |
||||||
|
import com.jme3.system.AppSettings; |
||||||
|
import com.jme3.texture.Texture2D; |
||||||
|
import com.jme3.ui.Picture; |
||||||
|
import java.io.FileInputStream; |
||||||
|
import java.io.FileNotFoundException; |
||||||
|
|
||||||
|
public class TestNewVideo extends SimpleApplication implements BusHandler { |
||||||
|
|
||||||
|
private Picture picture; |
||||||
|
private JmeVideoPipeline p; |
||||||
|
private int frame = 0; |
||||||
|
|
||||||
|
public static void main(String[] args){ |
||||||
|
TestNewVideo app = new TestNewVideo(); |
||||||
|
AppSettings settings = new AppSettings(true); |
||||||
|
// settings.setFrameRate(24);
|
||||||
|
app.setSettings(settings); |
||||||
|
app.start(); |
||||||
|
} |
||||||
|
|
||||||
|
private void createVideo(){ |
||||||
|
Debug.level = Debug.INFO; |
||||||
|
p = new JmeVideoPipeline(this); |
||||||
|
p.getBus().addHandler(this); |
||||||
|
try { |
||||||
|
p.inputStream = new FileInputStream("E:\\VideoTest.ogv"); |
||||||
|
} catch (FileNotFoundException ex) { |
||||||
|
ex.printStackTrace(); |
||||||
|
} |
||||||
|
p.setState(Pipeline.PLAY); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void simpleUpdate(float tpf){ |
||||||
|
// if (p == null)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
Texture2D tex = p.getTexture(); |
||||||
|
if (tex == null) |
||||||
|
return; |
||||||
|
|
||||||
|
if (picture != null){ |
||||||
|
synchronized (tex){ |
||||||
|
try { |
||||||
|
tex.wait(); |
||||||
|
} catch (InterruptedException ex) { |
||||||
|
// ignore
|
||||||
|
} |
||||||
|
tex.getImage().setUpdateNeeded(); |
||||||
|
renderer.setTexture(0, tex); |
||||||
|
((VideoTexture)tex).free(); |
||||||
|
System.out.println("PLAY : " + (frame++)); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
picture = new Picture("VideoPicture", true); |
||||||
|
picture.setPosition(0, 0); |
||||||
|
picture.setWidth(settings.getWidth()); |
||||||
|
picture.setHeight(settings.getHeight()); |
||||||
|
picture.setTexture(assetManager, tex, false); |
||||||
|
rootNode.attachChild(picture); |
||||||
|
} |
||||||
|
|
||||||
|
public void simpleInitApp() { |
||||||
|
// start video playback
|
||||||
|
createVideo(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void destroy(){ |
||||||
|
if (p != null){ |
||||||
|
p.setState(Pipeline.STOP); |
||||||
|
p.shutDown(); |
||||||
|
} |
||||||
|
super.destroy(); |
||||||
|
} |
||||||
|
|
||||||
|
public void handleMessage(Message msg) { |
||||||
|
switch (msg.getType()){ |
||||||
|
case Message.EOS: |
||||||
|
Debug.log(Debug.INFO, "EOS: playback ended"); |
||||||
|
/* |
||||||
|
enqueue(new Callable<Void>(){ |
||||||
|
public Void call() throws Exception { |
||||||
|
rootNode.detachChild(picture); |
||||||
|
p.setState(Element.STOP); |
||||||
|
p.shutDown(); |
||||||
|
p = null; |
||||||
|
return null; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
Texture2D tex = p.getTexture(); |
||||||
|
synchronized (tex){ |
||||||
|
tex.notifyAll(); |
||||||
|
} |
||||||
|
*/ |
||||||
|
break; |
||||||
|
case Message.STREAM_STATUS: |
||||||
|
Debug.info(msg.toString()); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
package com.jme3.newvideo; |
||||||
|
|
||||||
|
import com.fluendo.jst.Buffer; |
||||||
|
import com.fluendo.jst.Caps; |
||||||
|
import com.fluendo.jst.Pad; |
||||||
|
import com.fluendo.jst.Sink; |
||||||
|
import com.fluendo.utils.Debug; |
||||||
|
import com.jme3.texture.Image; |
||||||
|
import com.jme3.texture.Texture2D; |
||||||
|
|
||||||
|
public class TextureVideoSink extends Sink { |
||||||
|
|
||||||
|
private Texture2D outTex; |
||||||
|
private int width, height; |
||||||
|
|
||||||
|
private int frame = 0; |
||||||
|
|
||||||
|
public TextureVideoSink(String name) { |
||||||
|
super(); |
||||||
|
setName(name); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected boolean setCapsFunc(Caps caps) { |
||||||
|
String mime = caps.getMime(); |
||||||
|
if (!mime.equals("video/raw")) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
width = caps.getFieldInt("width", -1); |
||||||
|
height = caps.getFieldInt("height", -1); |
||||||
|
|
||||||
|
if (width == -1 || height == -1) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// aspectX = caps.getFieldInt("aspect_x", 1);
|
||||||
|
// aspectY = caps.getFieldInt("aspect_y", 1);
|
||||||
|
//
|
||||||
|
// if (!ignoreAspect) {
|
||||||
|
// Debug.log(Debug.DEBUG, this + " dimension: " + width + "x" + height + ", aspect: " + aspectX + "/" + aspectY);
|
||||||
|
//
|
||||||
|
// if (aspectY > aspectX) {
|
||||||
|
// height = height * aspectY / aspectX;
|
||||||
|
// } else {
|
||||||
|
// width = width * aspectX / aspectY;
|
||||||
|
// }
|
||||||
|
// Debug.log(Debug.DEBUG, this + " scaled source: " + width + "x" + height);
|
||||||
|
// }
|
||||||
|
|
||||||
|
outTex = new Texture2D(); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected int preroll(Buffer buf) { |
||||||
|
return render(buf); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected int render(Buffer buf) { |
||||||
|
if (buf.duplicate) |
||||||
|
return Pad.OK; |
||||||
|
|
||||||
|
Debug.log(Debug.DEBUG, this.getName() + " starting buffer " + buf); |
||||||
|
if (buf.object instanceof Image){ |
||||||
|
synchronized (outTex){ |
||||||
|
outTex.setImage( (Image) buf.object ); |
||||||
|
outTex.notifyAll(); |
||||||
|
System.out.println("PUSH : " + (frame++)); |
||||||
|
} |
||||||
|
} else { |
||||||
|
System.out.println(this + ": unknown buffer received " + buf.object); |
||||||
|
return Pad.ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
if (outTex == null) { |
||||||
|
return Pad.NOT_NEGOTIATED; |
||||||
|
} |
||||||
|
|
||||||
|
Debug.log(Debug.DEBUG, this.getName() + " done with buffer " + buf); |
||||||
|
return Pad.OK; |
||||||
|
} |
||||||
|
|
||||||
|
public String getFactoryName() { |
||||||
|
return "texturevideosink"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public java.lang.Object getProperty(String name) { |
||||||
|
if (name.equals("texture")) { |
||||||
|
return outTex; |
||||||
|
} else { |
||||||
|
return super.getProperty(name); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package com.jme3.newvideo; |
||||||
|
|
||||||
|
import com.jme3.texture.Image; |
||||||
|
import com.jme3.texture.Image.Format; |
||||||
|
import com.jme3.texture.Texture2D; |
||||||
|
import com.jme3.util.BufferUtils; |
||||||
|
import java.util.concurrent.BlockingQueue; |
||||||
|
|
||||||
|
public final class VideoTexture extends Texture2D { |
||||||
|
|
||||||
|
private BlockingQueue<VideoTexture> ownerQueue; |
||||||
|
|
||||||
|
public VideoTexture(int width, int height, Format format, BlockingQueue<VideoTexture> ownerQueue){ |
||||||
|
super(new Image(format, width, height, |
||||||
|
BufferUtils.createByteBuffer(width*height*format.getBitsPerPixel()/8))); |
||||||
|
this.ownerQueue = ownerQueue; |
||||||
|
} |
||||||
|
|
||||||
|
public void free(){ |
||||||
|
try { |
||||||
|
ownerQueue.put(this); |
||||||
|
} catch (InterruptedException ex) { |
||||||
|
ex.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,109 @@ |
|||||||
|
package com.jme3.newvideo; |
||||||
|
|
||||||
|
import com.fluendo.jheora.YUVBuffer; |
||||||
|
import com.fluendo.jst.Buffer; |
||||||
|
import com.fluendo.jst.Element; |
||||||
|
import com.fluendo.jst.Event; |
||||||
|
import com.fluendo.jst.Pad; |
||||||
|
import com.jme3.app.Application; |
||||||
|
import com.jme3.texture.Image.Format; |
||||||
|
import java.awt.image.FilteredImageSource; |
||||||
|
import java.lang.reflect.Field; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.util.concurrent.ArrayBlockingQueue; |
||||||
|
import java.util.concurrent.BlockingQueue; |
||||||
|
import java.util.concurrent.Callable; |
||||||
|
|
||||||
|
public class YUV2Texture extends Element { |
||||||
|
|
||||||
|
private YUVConv conv = new YUVConv(); |
||||||
|
private int width, height; |
||||||
|
private BlockingQueue<VideoTexture> frameQueue; |
||||||
|
private Application app; |
||||||
|
|
||||||
|
private int frame = 0; |
||||||
|
|
||||||
|
private YUVBuffer getYUVBuffer(Buffer buf){ |
||||||
|
if (buf.object instanceof FilteredImageSource) { |
||||||
|
FilteredImageSource imgSrc = (FilteredImageSource) buf.object; |
||||||
|
try { |
||||||
|
Field srcField = imgSrc.getClass().getDeclaredField("src"); |
||||||
|
srcField.setAccessible(true); |
||||||
|
return (YUVBuffer) srcField.get(imgSrc); |
||||||
|
} catch (Exception e){ |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
}else if (buf.object instanceof YUVBuffer){ |
||||||
|
return (YUVBuffer) buf.object; |
||||||
|
}else{ |
||||||
|
throw new RuntimeException("Expected buffer"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private VideoTexture decode(YUVBuffer yuv){ |
||||||
|
if (frameQueue == null){ |
||||||
|
frameQueue = new ArrayBlockingQueue<VideoTexture>(20); |
||||||
|
for (int i = 0; i < 20; i++){ |
||||||
|
VideoTexture img = new VideoTexture(yuv.y_width, yuv.y_height, Format.RGBA8, frameQueue); |
||||||
|
frameQueue.add(img); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
final VideoTexture videoTex = frameQueue.take(); |
||||||
|
ByteBuffer outBuf = videoTex.getImage().getData(0); |
||||||
|
conv.convert(yuv, 0, 0, yuv.y_width, yuv.y_height); |
||||||
|
outBuf.clear(); |
||||||
|
outBuf.asIntBuffer().put(conv.getRGBData()).clear(); |
||||||
|
|
||||||
|
app.enqueue( new Callable<Void>() { |
||||||
|
public Void call() throws Exception { |
||||||
|
videoTex.getImage().setUpdateNeeded(); |
||||||
|
app.getRenderer().setTexture(0, videoTex); |
||||||
|
return null; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return videoTex; |
||||||
|
} catch (InterruptedException ex) { |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private Pad srcPad = new Pad(Pad.SRC, "src") { |
||||||
|
@Override |
||||||
|
protected boolean eventFunc(Event event) { |
||||||
|
return sinkPad.pushEvent(event); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
private Pad sinkPad = new Pad(Pad.SINK, "sink") { |
||||||
|
@Override |
||||||
|
protected boolean eventFunc(Event event) { |
||||||
|
return srcPad.pushEvent(event); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected int chainFunc (Buffer buf) { |
||||||
|
YUVBuffer yuv = getYUVBuffer(buf); |
||||||
|
buf.object = decode(yuv); |
||||||
|
System.out.println("DECODE: " + (frame++)); |
||||||
|
return srcPad.push(buf); |
||||||
|
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
public YUV2Texture(Application app) { |
||||||
|
super("YUV2Texture"); |
||||||
|
addPad(srcPad); |
||||||
|
addPad(sinkPad); |
||||||
|
this.app = app; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getFactoryName() { |
||||||
|
return "yuv2tex"; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.jme3.newvideo; |
||||||
|
|
||||||
|
import com.fluendo.jheora.YUVBuffer; |
||||||
|
|
||||||
|
@Deprecated |
||||||
|
public final class YUVConv { |
||||||
|
|
||||||
|
private int[] pixels; |
||||||
|
|
||||||
|
private static final int VAL_RANGE = 256; |
||||||
|
private static final int SHIFT = 16; |
||||||
|
|
||||||
|
private static final int CR_FAC = (int) (1.402 * (1 << SHIFT)); |
||||||
|
private static final int CB_FAC = (int) (1.772 * (1 << SHIFT)); |
||||||
|
private static final int CR_DIFF_FAC = (int) (0.71414 * (1 << SHIFT)); |
||||||
|
private static final int CB_DIFF_FAC = (int) (0.34414 * (1 << SHIFT)); |
||||||
|
|
||||||
|
private static int[] r_tab = new int[VAL_RANGE * 3]; |
||||||
|
private static int[] g_tab = new int[VAL_RANGE * 3]; |
||||||
|
private static int[] b_tab = new int[VAL_RANGE * 3]; |
||||||
|
|
||||||
|
static { |
||||||
|
setupRgbYuvAccelerators(); |
||||||
|
} |
||||||
|
|
||||||
|
private static final short clamp255(int val) { |
||||||
|
val -= 255; |
||||||
|
val = -(255 + ((val >> (31)) & val)); |
||||||
|
return (short) -((val >> 31) & val); |
||||||
|
} |
||||||
|
|
||||||
|
private static void setupRgbYuvAccelerators() { |
||||||
|
for (int i = 0; i < VAL_RANGE * 3; i++) { |
||||||
|
r_tab[i] = clamp255(i - VAL_RANGE); |
||||||
|
g_tab[i] = clamp255(i - VAL_RANGE) << 8; |
||||||
|
b_tab[i] = clamp255(i - VAL_RANGE) << 16; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public YUVConv(){ |
||||||
|
} |
||||||
|
|
||||||
|
public int[] getRGBData(){ |
||||||
|
return pixels; |
||||||
|
} |
||||||
|
|
||||||
|
public void convert(YUVBuffer yuv, int xOff, int yOff, int width, int height) { |
||||||
|
if (pixels == null){ |
||||||
|
pixels = new int[width*height]; |
||||||
|
} |
||||||
|
// Set up starting values for YUV pointers
|
||||||
|
int YPtr = yuv.y_offset + xOff + yOff * (yuv.y_stride); |
||||||
|
int YPtr2 = YPtr + yuv.y_stride; |
||||||
|
int UPtr = yuv.u_offset + xOff/2 + (yOff/2)*(yuv.uv_stride); |
||||||
|
int VPtr = yuv.v_offset + xOff/2 + (yOff/2)*(yuv.uv_stride); |
||||||
|
int RGBPtr = 0; |
||||||
|
int RGBPtr2 = width; |
||||||
|
int width2 = width / 2; |
||||||
|
int height2 = height / 2; |
||||||
|
|
||||||
|
// Set the line step for the Y and UV planes and YPtr2
|
||||||
|
int YStep = yuv.y_stride * 2 - (width2) * 2; |
||||||
|
int UVStep = yuv.uv_stride - (width2); |
||||||
|
int RGBStep = width; |
||||||
|
|
||||||
|
for (int i = 0; i < height2; i++) { |
||||||
|
for (int j = 0; j < width2; j++) { |
||||||
|
// groups of four pixels
|
||||||
|
int UFactor = yuv.data[UPtr++] - 128; |
||||||
|
int VFactor = yuv.data[VPtr++] - 128; |
||||||
|
int GFactor = UFactor * CR_DIFF_FAC + VFactor * CB_DIFF_FAC - (VAL_RANGE<<SHIFT); |
||||||
|
|
||||||
|
UFactor = UFactor * CR_FAC + (VAL_RANGE<<SHIFT); |
||||||
|
VFactor = VFactor * CB_FAC + (VAL_RANGE<<SHIFT); |
||||||
|
|
||||||
|
int YVal = yuv.data[YPtr] << SHIFT; |
||||||
|
pixels[RGBPtr] = r_tab[(YVal + VFactor)>>SHIFT] | |
||||||
|
b_tab[(YVal + UFactor)>>SHIFT] | |
||||||
|
g_tab[(YVal - GFactor)>>SHIFT]; |
||||||
|
|
||||||
|
YVal = yuv.data[YPtr+1] << SHIFT; |
||||||
|
pixels[RGBPtr+1] = r_tab[(YVal + VFactor)>>SHIFT] | |
||||||
|
b_tab[(YVal + UFactor)>>SHIFT] | |
||||||
|
g_tab[(YVal - GFactor)>>SHIFT]; |
||||||
|
|
||||||
|
YVal = yuv.data[YPtr2] << SHIFT; |
||||||
|
pixels[RGBPtr2] = r_tab[(YVal + VFactor)>>SHIFT] | |
||||||
|
b_tab[(YVal + UFactor)>>SHIFT] | |
||||||
|
g_tab[(YVal - GFactor)>>SHIFT]; |
||||||
|
|
||||||
|
YVal = yuv.data[YPtr2+1] << SHIFT; |
||||||
|
pixels[RGBPtr2+1] = r_tab[(YVal + VFactor)>>SHIFT] | |
||||||
|
b_tab[(YVal + UFactor)>>SHIFT] | |
||||||
|
g_tab[(YVal - GFactor)>>SHIFT]; |
||||||
|
|
||||||
|
YPtr += 2; |
||||||
|
YPtr2 += 2; |
||||||
|
RGBPtr += 2; |
||||||
|
RGBPtr2 += 2; |
||||||
|
} |
||||||
|
|
||||||
|
// Increment the various pointers
|
||||||
|
YPtr += YStep; |
||||||
|
YPtr2 += YStep; |
||||||
|
UPtr += UVStep; |
||||||
|
VPtr += UVStep; |
||||||
|
RGBPtr += RGBStep; |
||||||
|
RGBPtr2 += RGBStep; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue