我正在寻找一种使用 JavaFx 显示 RTP JPEG 流的解决方案。我可以显示文件中的 jpeg 并接收 RTP JPEG 流并将其拆分以识别RFC2435中指定的所有参数和数据 但我不知道如何将我的 JPEG 数组转换为可显示的图像。我不想自己实现 JPEG 解码器。任何想法?
1 回答
利用 JavaFX 的内置 jpeg 解码器,它应该能够在Image 构造函数中解码 jpeg 图像。
class MJPEGViewer extends ImageView {
MJPEGViewer() {
// setup a thread which processes the input stream.
// the processing thread invokes onNewData for each new frame.
}
private void onNewData(byte[] jpegData) {
imageView.set(
new Image(
new ByteArrayInputStream(jpegData);
)
);
}
}
jpegData 是一个字节数组,假定包含从 RTP 流中提取的帧的 JFIF 数据。
可执行样本
这是一个 mjpeg 电影播放器,播放来自:http: //inst.eecs.berkeley.edu/~ee122/sp06/ProgAsgns/movie.Mjpeg的电影
基于编程作业 5 中的视频流课程:使用 RTSP 和 RTP 流式传输视频(我希望这不是您的家庭作业课程)。
根据视频流类描述,它是一种“专有的 MJPEG 格式”,因此您需要根据 RFC2435 对符合标准的格式进行自己的解码。
该播放器可以正常工作,但在正确解码我尚未调查的 JPEG 时确实存在问题。“专有 MJPEG 格式”示例影片中的 JPEG 未正确编码,或者 JavaFX JPEG 编解码器在解码帧时出错。可见的伪影是您可以看到图像,但图像的颜色不正确(具有粉红色阴影)。这可能是RT-14647 JPEG 图像显示不正确的一个实例因为视频中的粉红色阴影在错误中显示的错误解码 JPEG 中看起来相同。您可以在下面的示例代码呈现的视频截图中清楚地看到粉色阴影。该错误仅影响一些 JPEG 图像(我在 JavaFX 中使用的绝大多数 JPEG 图像都显示良好)。因此,您只需要尝试使用您的视频流来查看 JavaFX jpeg 解码器是否为您正确解码 jpeg 图像。
而不是每次都替换图像视图中的图像,使用WritableImage并直接更新它的像素缓冲区可能更有效,但是蛮力替换图像方法对我来说似乎工作正常。
import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.*;
import java.util.Arrays;
public class MjpegPlayer extends Application {
public static void main(String[] args) { Application.launch(MjpegPlayer.class); }
// ADJUST THIS LOCATION TO SET THE LOCATION OF YOUR MOVIE FILE!!
private static final String MOVIE_FILE = "/Users/lilyshard/dev/playfx/src/fruits/movie.Mjpeg";
private VideoStream vs;
@Override public void start(Stage stage) throws Exception {
vs = new VideoStream(MOVIE_FILE);
final ImageView viewer = new ImageView();
final Timeline timeline = createTimeline(viewer);
VBox layout = new VBox(20);
layout.setStyle("-fx-background-color: cornsilk;");
layout.setAlignment(Pos.CENTER);
layout.getChildren().setAll(
viewer,
createControls(timeline)
);
stage.setScene(new Scene(layout, 400, 400));
stage.show();
timeline.play();
}
private Timeline createTimeline(final ImageView viewer) {
final Timeline timeline = new Timeline();
final byte[] buf = new byte[15000];
timeline.getKeyFrames().setAll(
new KeyFrame(Duration.ZERO, new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent event) {
try {
int len = vs.getnextframe(buf);
if (len == -1) {
timeline.stop();
return;
}
viewer.setImage(
new Image(
new ByteArrayInputStream(
Arrays.copyOf(buf, len)
)
)
);
} catch (Exception e) {
e.printStackTrace();
}
}
}),
new KeyFrame(Duration.seconds(1.0/24))
);
timeline.setCycleCount(Timeline.INDEFINITE);
return timeline;
}
private HBox createControls(final Timeline timeline) {
Button play = new Button("Play");
play.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
timeline.play();
}
});
Button pause = new Button("Pause");
pause.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
timeline.pause();
}
});
Button restart = new Button("Restart");
restart.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
try {
timeline.stop();
vs = new VideoStream(MOVIE_FILE);
timeline.playFromStart();
} catch (Exception e) {
e.printStackTrace();
}
}
});
HBox controls = new HBox(10);
controls.setAlignment(Pos.CENTER);
controls.getChildren().setAll(
play,
pause,
restart
);
return controls;
}
}
class VideoStream {
FileInputStream fis; //video file
int frame_nb; //current frame nb
public VideoStream(String filename) throws Exception{
//init variables
fis = new FileInputStream(filename);
frame_nb = 0;
}
public int getnextframe(byte[] frame) throws Exception
{
int length = 0;
String length_string;
byte[] frame_length = new byte[5];
//read current frame length
fis.read(frame_length,0,5);
//transform frame_length to integer
length_string = new String(frame_length);
try {
length = Integer.parseInt(length_string);
} catch (Exception e) {
return -1;
}
return(fis.read(frame,0,length));
}
}
更新
我尝试在 Windows 7 上使用 Java 8u20 早期访问版本 11 再次运行该程序,并且视频播放良好,没有任何粉红色,所以我猜导致该问题的任何原因现在已经在以后的 Java 版本中得到修复。