javacv实现直播流
javacv从入门到入土系列,音视频入门有一点门槛的 延迟大概是2~4秒之间,
依赖
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.4-1.5.6</version>
</dependency>
视频采集可以使用摄像头或者什么的,我这里用了桌面录像
package top.lingkang.test.gui;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.Timer;
import java.util.TimerTask;
public class MyLive extends Application {
private static final int frameRate = 24;
private static boolean isStop = false;
private static TargetDataLine line;
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("lingkang-桌面录屏大师");
ImageView imageVideo = new ImageView();
imageVideo.setFitWidth(800);
imageVideo.setFitHeight(600);
Button button = new Button("停止录制");
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
isStop = true;
if (line != null) {
try {
line.close();
} catch (Exception e) {
}
}
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("info");
alert.setHeaderText("已经停止录制");
alert.setOnCloseRequest(event1 -> alert.hide());
alert.showAndWait();
}
});
VBox box = new VBox();
box.getChildren().addAll(button, imageVideo);
primaryStage.setScene(new Scene(box));
primaryStage.setHeight(600);
primaryStage.setWidth(800);
primaryStage.show();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
isStop = true;
System.exit(0);
}
});
FrameGrabber grabber = new FFmpegFrameGrabber("desktop");
grabber.setFormat("gdigrab");
grabber.setFrameRate(frameRate);
grabber.setImageHeight(600);
grabber.setImageWidth(800);
grabber.start();
File file = new File("D://output.avi");
if (file.exists())
file.delete();
final FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
"rtmp://10.8.4.191/live/livestream",
grabber.getImageWidth(), grabber.getImageHeight(), 2);
recorder.setInterleaved(true);
recorder.setVideoOption("tune", "zerolatency");
recorder.setVideoOption("preset", "ultrafast");
recorder.setFrameRate(frameRate);
recorder.setGopSize(frameRate * 2);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setVideoOption("crf", "28");
recorder.setVideoBitrate(2000000);
recorder.setFormat("flv");
recorder.setAudioOption("crf", "0");
recorder.setAudioQuality(0);
recorder.setAudioBitrate(192000);
recorder.setSampleRate(44100);
recorder.setAudioChannels(2);
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.start();
AudioFormat audioFormat = new AudioFormat(44100.0F, 16, 2, true, false);
DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
line = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
new Thread(new Runnable() {
@Override
public void run() {
try {
line.open(audioFormat);
line.start();
final int sampleRate = (int) audioFormat.getSampleRate();
final int numChannels = audioFormat.getChannels();
final int audioBufferSize = sampleRate * numChannels;
final byte[] audioBytes = new byte[audioBufferSize];
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
if (isStop) {
line.stop();
line.close();
System.out.println("已经停止!");
timer.cancel();
}
int readLenth = 0;
while (readLenth == 0)
readLenth = line.read(audioBytes, 0, line.available());
int rl = readLenth / 2;
short[] samples = new short[rl];
ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, rl);
recorder.recordSamples(sampleRate, numChannels, sBuff);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 1000, 1000 / frameRate);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Frame frame = null;
while ((frame = grabber.grab()) != null) {
if (isStop) {
try {
recorder.stop();
grabber.stop();
recorder.release();
grabber.release();
} catch (Exception e) {
e.printStackTrace();
}
break;
}
recorder.record(frame);
Image convert = new JavaFXFrameConverter().convert(frame);
imageVideo.setImage(convert);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
public static void main(String[] args) {
launch(args);
}
}
可以用docker起一个srs进行推流播放。
# 先启动
docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 \
ccr.ccs.tencentyun.com/ossrs/srs:4
再启动推流: http://10.8.4.191:8080/players/srs_player.html?schema=http
|