xuggler和謙遜的視頻可以得到旋轉元,xuggler你可以看到下面的代碼的兩個:
public class MultimediaContentConverterVideo {
public void convertOriginal(String urlIn, String urlOut, boolean debug) throws IOException {
String workingPath = FilenameUtils.getFullPath(urlIn);
String filenamePrefix = FilenameUtils.getBaseName(urlIn);
// create a media reader
IMediaReader reader = ToolFactory.makeReader(urlIn);
// stipulate that we want BufferedImages created in BGR 24bit color space
reader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);
// create a writer which receives the decoded media from
// reader, encodes it and writes it out to the specified file
IMediaWriter writer = ToolFactory.makeWriter(urlOut, reader);
// add a debug listener to the writer to see media writer events
if (debug) {
writer.addListener(ToolFactory.makeDebugListener());
}
// read and decode packets from the source file and
// then encode and write out data to the output file
VideoRotator rotator = new VideoRotator();
reader.addListener(rotator);
rotator.addListener(writer);
while (reader.readPacket() == null);
}
private class VideoRotator extends MediaToolAdapter {
private int rotate = 0;
@Override
public void onVideoPicture(IVideoPictureEvent event) {
BufferedImage img = event.getImage();
rotateImage(rotate, img);
super.onVideoPicture(event);
}
private static BufferedImage rotateImage(int rotate, BufferedImage img) {
if (rotate == 0 || img == null) {
return img;
}
int width = img.getWidth();
int height = img.getHeight();
int new_w = 0, new_h = 0;
int new_radian = rotate;
if (rotate <= 90) {
new_w = (int)(width * Math.cos(Math.toRadians(new_radian)) + height * Math.sin(Math.toRadians(new_radian)));
new_h = (int)(height * Math.cos(Math.toRadians(new_radian)) + width * Math.sin(Math.toRadians(new_radian)));
} else if (rotate <= 180) {
new_radian = rotate - 90;
new_w = (int)(height * Math.cos(Math.toRadians(new_radian)) + width * Math.sin(Math.toRadians(new_radian)));
new_h = (int)(width * Math.cos(Math.toRadians(new_radian)) + height * Math.sin(Math.toRadians(new_radian)));
} else if (rotate <= 270) {
new_radian = rotate - 180;
new_w = (int)(width * Math.cos(Math.toRadians(new_radian)) + height * Math.sin(Math.toRadians(new_radian)));
new_h = (int)(height * Math.cos(Math.toRadians(new_radian)) + width * Math.sin(Math.toRadians(new_radian)));
} else {
new_radian = rotate - 270;
new_w = (int)(height * Math.cos(Math.toRadians(new_radian)) +
width * Math.sin(Math.toRadians(new_radian)));
new_h = (int)(width * Math.cos(Math.toRadians(new_radian)) +
height * Math.sin(Math.toRadians(new_radian)));
}
BufferedImage toStore = new
BufferedImage(new_w, new_h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = toStore.createGraphics();
AffineTransform affineTransform = new AffineTransform();
affineTransform.rotate(Math.toRadians(rotate), width/2, height/2);
if (rotate != 180) {
AffineTransform translationTransform =
findTranslation(affineTransform, img, rotate);
affineTransform.preConcatenate(translationTransform);
}
g.setColor(Color.WHITE);
g.fillRect(0, 0, new_w, new_h);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawRenderedImage(img, affineTransform);
g.dispose();
return toStore;
}
private static AffineTransform findTranslation(AffineTransform at,
BufferedImage bi, int angle) { //45
Point2D p2din, p2dout;
double ytrans = 0.0, xtrans = 0.0;
if (angle <= 90) {
p2din = new Point2D.Double(0.0, 0.0);
p2dout = at.transform(p2din, null);
ytrans = p2dout.getY();
p2din = new Point2D.Double(0, bi.getHeight());
p2dout = at.transform(p2din, null);
xtrans = p2dout.getX();
}
/*else if(angle<=135){
p2din = new Point2D.Double(0.0, bi.getHeight());
p2dout = at.transform(p2din, null);
ytrans = p2dout.getY();
p2din = new Point2D.Double(bi.getWidth(),bi.getHeight());
p2dout = at.transform(p2din, null);
xtrans = p2dout.getX();
}*/
else if (angle <= 180) {
p2din = new Point2D.Double(0.0, bi.getHeight());
p2dout = at.transform(p2din, null);
ytrans = p2dout.getY();
p2din = new Point2D.Double(bi.getWidth(), bi.getHeight());
p2dout = at.transform(p2din, null);
xtrans = p2dout.getX();
}
/*else if(angle<=225){
p2din = new Point2D.Double(bi.getWidth(), bi.getHeight());
p2dout = at.transform(p2din, null);
ytrans = p2dout.getY();
p2din = new Point2D.Double(bi.getWidth(),0.0);
p2dout = at.transform(p2din, null);
xtrans = p2dout.getX();
}*/
else if (angle <= 270) {
p2din = new Point2D.Double(bi.getWidth(), bi.getHeight());
p2dout = at.transform(p2din, null);
ytrans = p2dout.getY();
p2din = new Point2D.Double(bi.getWidth(), 0.0);
p2dout = at.transform(p2din, null);
xtrans = p2dout.getX();
} else {
p2din = new Point2D.Double(bi.getWidth(), 0.0);
p2dout = at.transform(p2din, null);
ytrans = p2dout.getY();
p2din = new Point2D.Double(0.0, 0.0);
p2dout = at.transform(p2din, null);
xtrans = p2dout.getX();
}
AffineTransform tat = new AffineTransform();
tat.translate(-xtrans, -ytrans);
return tat;
}
@Override
public void onAddStream(IAddStreamEvent event) {
int streamIndex = event.getStreamIndex();
IStream stream = event.getSource().getContainer().getStream(streamIndex);
IStreamCoder streamCoder = event.getSource().getContainer().getStream(streamIndex).getStreamCoder();
if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_AUDIO) {
streamCoder.setSampleRate(44100);
} else if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) {
String metaRotate = stream.getMetaData().getValue(META_KEY_ROTATE);
if (metaRotate != null && metaRotate.matches("\\d+")) {
rotate = Integer.valueOf(metaRotate);
}
}
super.onAddStream(event);
}
}
}
,這裏的起源代碼後Xuggler, iPhone/iPad video rotation,我改變了旋轉代碼;
不起眼的視頻可以從DemuxerStream類旋轉元數據,請參見下面的代碼:
public class HumbleVideoHelper {
private static String META_KEY_ROTATE = "rotate";
public static class VideoInfo {
private Long fileSize;
private Integer frameWidth;
private Integer frameHeight;
private Long duration;
private BufferedImage firstFrameImage;
private int rotation;
public Long getFileSize() {
return fileSize;
}
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
public Integer getFrameWidth() {
return frameWidth;
}
public void setFrameWidth(Integer frameWidth) {
this.frameWidth = frameWidth;
}
public Integer getFrameHeight() {
return frameHeight;
}
public void setFrameHeight(Integer frameHeight) {
this.frameHeight = frameHeight;
}
public Long getDuration() {
return duration;
}
public void setDuration(Long duration) {
this.duration = duration;
}
public BufferedImage getFirstFrameImage() {
return firstFrameImage;
}
public void setFirstFrameImage(BufferedImage firstFrameImage) {
this.firstFrameImage = firstFrameImage;
}
public int getRotation() {
return rotation;
}
public void setRotation(int rotation) {
this.rotation = rotation;
}
@Override
public String toString() {
return "VideoInfo{" +
"fileSize=" + fileSize +
", frameWidth=" + frameWidth +
", frameHeight=" + frameHeight +
", duration=" + duration +
", firstFrameImage=" + firstFrameImage +
", rotation=" + rotation +
'}';
}
}
private String url;
private VideoInfo videoInfo;
private Demuxer demuxer;
private HumbleVideoHelper(String url) {
this.url = url;
}
public static HumbleVideoHelper with(String url) {
return new HumbleVideoHelper(url);
}
private void init() throws IOException, InterruptedException {
if (demuxer == null) {
demuxer = Demuxer.make();
demuxer.open(url, null, false,
true, null, null);
}
}
public VideoInfo parse(boolean closeAfterParse) throws IOException, InterruptedException {
if (videoInfo != null) {
return videoInfo;
}
init();
/*
* Iterate through the streams to find the first video stream
*/
int videoStreamId = -1;
long streamStartTime = Global.NO_PTS;
Decoder videoDecoder = null;
DemuxerStream demuxerStream = getVideoDemuxerStream(demuxer);
if (demuxerStream != null) {
videoStreamId = demuxerStream.getIndex();
streamStartTime = demuxerStream.getStartTime();
videoDecoder = demuxerStream.getDecoder();
}
if (videoStreamId == -1) {
throw new RuntimeException("could not find video stream in container: " + url);
}
BufferedImage image = getFirstFrameBufferedImage(demuxer, videoStreamId, streamStartTime, videoDecoder);
videoInfo = new VideoInfo();
setVideoInfoDuration(videoInfo);
videoInfo.setFileSize(demuxer.getFileSize());
videoInfo.setFrameHeight(videoDecoder.getHeight());
videoInfo.setFrameWidth(videoDecoder.getWidth());
videoInfo.setFirstFrameImage(image);
videoInfo.setRotation(NumberUtils.toInt(demuxerStream.getMetaData().getValue(META_KEY_ROTATE)));
if (closeAfterParse) {
close();
}
return videoInfo;
}
private void setVideoInfoDuration(VideoInfo videoInfo) {
videoInfo.setDuration((long)(demuxer.getDuration() * 1000.0/Global.DEFAULT_PTS_PER_SECOND));
}
private static BufferedImage getFirstFrameBufferedImage(Demuxer demuxer, int videoStreamId, long streamStartTime,
Decoder videoDecoder)
throws InterruptedException, IOException {
/*
* Now we have found the audio stream in this file. Let's open up our decoder so it can
* do work.
*/
videoDecoder.open(null, null);
final MediaPicture picture = MediaPicture.make(
videoDecoder.getWidth(),
videoDecoder.getHeight(),
videoDecoder.getPixelFormat());
/* A converter object we'll use to convert the picture in the video to a BGR_24 format that Java Swing
can work with. You can still access the data directly in the MediaPicture if you prefer, but this
abstracts away from this demo most of that byte-conversion work. Go read the source code for the
converters if you're a glutton for punishment.
*/
final MediaPictureConverter converter =
MediaPictureConverterFactory.createConverter(
MediaPictureConverterFactory.HUMBLE_BGR_24,
picture);
BufferedImage image = null;
// Calculate the time BEFORE we start playing.
long systemStartTime = System.nanoTime();
// Set units for the system time, which because we used System.nanoTime will be in nanoseconds.
final Rational systemTimeBase = Rational.make(1, 1000000000);
// All the MediaPicture objects decoded from the videoDecoder will share this timebase.
final Rational streamTimebase = videoDecoder.getTimeBase();
/*
Now, we start walking through the container looking at each packet. This
is a decoding loop, and as you work with Humble you'll write a lot
of these.
Notice how in this loop we reuse all of our objects to avoid
reallocating them. Each call to Humble resets objects to avoid
unnecessary reallocation.
*/
final MediaPacket packet = MediaPacket.make();
while (demuxer.read(packet) >= 0) {
/*
Now we have a packet, let's see if it belongs to our video stream
*/
if (packet.getStreamIndex() == videoStreamId) {
/*
A packet can actually contain multiple sets of samples (or frames of samples
in decoding speak). So, we may need to call decode multiple
times at different offsets in the packet's data. We capture that here.
*/
int offset = 0;
int bytesRead = 0;
do {
bytesRead += videoDecoder.decode(picture, packet, offset);
if (picture.isComplete()) {
image = getVideoImageAtCorrectTime(streamStartTime, picture,
converter, image, systemStartTime, systemTimeBase,
streamTimebase);
}
offset += bytesRead;
if (image != null) {
break;
}
} while (offset < packet.getSize());
}
}
// Some video decoders (especially advanced ones) will cache
// video data before they begin decoding, so when you are done you need
// to flush them. The convention to flush Encoders or Decoders in Humble Video
// is to keep passing in null until incomplete samples or packets are returned.
do {
if (image != null) {
break;
}
videoDecoder.decode(picture, null, 0);
if (picture.isComplete()) {
image = getVideoImageAtCorrectTime(streamStartTime, picture, converter,
null, systemStartTime, systemTimeBase, streamTimebase);
}
} while (picture.isComplete());
return image;
}
public VideoInfo parseDurationAndSize(boolean closeAfterParse) throws IOException, InterruptedException {
init();
VideoInfo videoInfo = new VideoInfo();
videoInfo.setFileSize(demuxer.getFileSize());
setVideoInfoDuration(videoInfo);
if (closeAfterParse) {
close();
}
return videoInfo;
}
public VideoInfo parseWithoutImage(boolean closeAfterParse) throws IOException, InterruptedException {
init();
/*
* Iterate through the streams to find the first video stream
*/
int videoStreamId = -1;
Decoder videoDecoder = null;
DemuxerStream demuxerStream = getVideoDemuxerStream(demuxer);
if (demuxerStream != null) {
videoStreamId = demuxerStream.getIndex();
videoDecoder = demuxerStream.getDecoder();
}
if (videoStreamId == -1) {
throw new RuntimeException("could not find video stream in container: " + url);
}
VideoInfo videoInfo = new VideoInfo();
setVideoInfoDuration(videoInfo);
videoInfo.setFileSize(demuxer.getFileSize());
videoInfo.setFrameHeight(videoDecoder.getHeight());
videoInfo.setFrameWidth(videoDecoder.getWidth());
if (closeAfterParse) {
close();
}
return videoInfo;
}
private static DemuxerStream getVideoDemuxerStream(Demuxer demuxer)
throws IOException, InterruptedException {
/*
* Query how many streams the call to open found
*/
int numStreams = demuxer.getNumStreams();
for (int i = 0; i < numStreams; i++) {
final DemuxerStream stream = demuxer.getStream(i);
final Decoder decoder = stream.getDecoder();
if (decoder != null && decoder.getCodecType() == MediaDescriptor.Type.MEDIA_VIDEO) {
return stream;
}
}
return null;
}
/**
* Takes the video picture and displays it at the right time.
*/
private static BufferedImage getVideoImageAtCorrectTime(long streamStartTime,
final MediaPicture picture,
final MediaPictureConverter converter,
BufferedImage image, long systemStartTime,
final Rational systemTimeBase,
final Rational streamTimebase)
throws InterruptedException {
long streamTimestamp = picture.getTimeStamp();
// convert streamTimestamp into system units (i.e. nano-seconds)
streamTimestamp = systemTimeBase.rescale(streamTimestamp - streamStartTime, streamTimebase);
// get the current clock time, with our most accurate clock
long systemTimestamp = System.nanoTime();
// loop in a sleeping loop until we're within 1 ms of the time for that video frame.
// a real video player needs to be much more sophisticated than this.
while (streamTimestamp > (systemTimestamp - systemStartTime + 1000000)) {
Thread.sleep(1);
systemTimestamp = System.nanoTime();
}
// finally, convert the image from Humble format into Java images.
image = converter.toImage(image, picture);
// And ask the UI thread to repaint with the new image.
// window.setImage(image);
return image;
}
/**
* It is good practice to close demuxers when you're done to free
* up file handles. Humble will EVENTUALLY detect if nothing else
* references this demuxer and close it then, but get in the habit
* of cleaning up after yourself, and your future girlfriend/boyfriend
* will appreciate it.
*/
public void close() {
if (demuxer != null) {
try {
demuxer.close();
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}
demuxer = null;
}
}