PS流转h264裸流的MP4~java代码实现
ps流视频转换为h264裸流java代码实现
·
前情提要:
1,部署好摄像设备的网关
2,可以正常请求海康摄像头视频数据:
需求描述: 需要下载海康历史视频数据, 设备检测到一些事件后会发送告警信息,收到告警后将这告警期间的这段视频保存下来;
**由于海康只提供七天的保存时间,所以要将这报警期间的视频保存到obs中;
工具类:
package com.wyj.government.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import java.io.*;
public class CommUtil {
private static final Logger log = LoggerFactory.getLogger(CommUtil.class);
public static CloseableHttpClient httpClient;
// private static final int CONNECT_TIMEOUT = common.HttpClientConfig.getHttpConnectTimeout();// 设置连接建立的超时时间为10s
// private static final int SOCKET_TIMEOUT = common.HttpClientConfig.getHttpSocketTimeout();
// private static final int MAX_CONN = common.HttpClientConfig.getHttpMaxPoolSize(); // 最大连接数
// private static final int Max_PRE_ROUTE = common.HttpClientConfig.getHttpMaxPoolSize();
// private static final int MAX_ROUTE = common.HttpClientConfig.getHttpMaxPoolSize();
/**
* PUT操作命令
*
* @param url
* @param Input
* @return
*/
public static String doPut(String host, short port, String UserName, String Password, String url, String Input) {
String PutUrl = "http://" + host + ":" + port + url;
HttpPut httpPut = new HttpPut(PutUrl);
httpPut.setEntity(new StringEntity(Input, "UTF-8"));
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(host, port),
new UsernamePasswordCredentials(UserName, Password));
httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
CloseableHttpResponse responseBody = null;
String response = "";
try {
// 由客户端执行(发送)Post请求
responseBody = httpClient.execute(httpPut);
// 从响应模型中获取响应实体
HttpEntity responseEntity = responseBody.getEntity();
System.out.println("响应状态为:" + responseBody.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
response = EntityUtils.toString(responseEntity);
System.out.println("响应内容为:\n" + response);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (responseBody != null) {
responseBody.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return response;
}
public static String doPost(String host, short port, String UserName, String Password, String url, String Input) {
String PostUrl = "http://" + host + ":" + port + url;
HttpPost httpPost = new HttpPost(PostUrl);
httpPost.setEntity(new StringEntity(Input, "UTF-8"));
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(host, port),
new UsernamePasswordCredentials(UserName, Password));
httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
CloseableHttpResponse responseBody = null;
String response = "";
try {
// 由客户端执行(发送)Post请求
responseBody = httpClient.execute(httpPost);
// 从响应模型中获取响应实体
HttpEntity responseEntity = responseBody.getEntity();
System.out.println("响应状态为:" + responseBody.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
response = EntityUtils.toString(responseEntity);
System.out.println("响应内容为:\n" + response);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (responseBody != null) {
responseBody.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return response;
}
//获得视频流
public static byte[] doStreamPost(String host, short port, String UserName, String Password, String url, String Input) {
String PostUrl = "http://" + host + ":" + port + url;
HttpPost httpPost = new HttpPost(PostUrl);
httpPost.setEntity(new StringEntity(Input, "UTF-8"));
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(host, port),
new UsernamePasswordCredentials(UserName, Password));
httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
byte[] byteArray = new byte[0];
CloseableHttpResponse responseBody = null;
try {
// 由客户端执行(发送)Post请求
responseBody = httpClient.execute(httpPost);
// 从响应模型中获取响应实体
HttpEntity responseEntity = responseBody.getEntity();
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
InputStream inputStream = responseEntity.getContent();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int bytesRead;
byte[] dataBuffer = new byte[1024];
while ((bytesRead = inputStream.read(dataBuffer, 0, dataBuffer.length)) != -1) {
buffer.write(dataBuffer, 0, bytesRead);
}
buffer.flush();
byteArray = buffer.toByteArray();
}
System.out.println("响应状态为:" + responseBody.getStatusLine());
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (responseBody != null) {
responseBody.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return byteArray;
}
public static String doGet(String host, short port, String UserName, String Password, String url) {
String GetUrl = "http://" + host + ":" + port + url;
HttpGet httpget = new HttpGet(GetUrl);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(host, port),
new UsernamePasswordCredentials(UserName, Password));
httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
CloseableHttpResponse responseBody = null;
String response = "";
try {
// 由客户端执行(发送)Post请求
responseBody = httpClient.execute(httpget);
// 从响应模型中获取响应实体
HttpEntity responseEntity = responseBody.getEntity();
System.out.println("响应状态为:" + responseBody.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
response = EntityUtils.toString(responseEntity);
System.out.println("响应内容为:\n" + response);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (responseBody != null) {
responseBody.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return response;
}
public static String doPostUploadPhoto(String host, short port, String UserName, String Password, String url, String Input) throws UnsupportedEncodingException {
String PostUrl = "http://" + host + ":" + port + url;
HttpPost httpPost = new HttpPost(PostUrl);
// 创建 MultipartEntityBuilder 实例
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addTextBody("faceURL", Input, ContentType.APPLICATION_JSON);
// 添加图片文件参数
File file = new File("C:\\Users\\liujian54\\Desktop\\FDLib.jpg");
builder.addBinaryBody("img", file, ContentType.create("image/jpeg"), file.getName());
HttpEntity multipart = builder.build();
// 设置请求体
httpPost.setEntity(multipart);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(host, port),
new UsernamePasswordCredentials(UserName, Password));
httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
CloseableHttpResponse responseBody = null;
String response = "";
try {
// 由客户端执行(发送)Post请求
responseBody = httpClient.execute(httpPost);
// 从响应模型中获取响应实体
HttpEntity responseEntity = responseBody.getEntity();
System.out.println("响应状态为:" + responseBody.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
response = EntityUtils.toString(responseEntity);
System.out.println("响应内容为:\n" + response);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (responseBody != null) {
responseBody.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return response;
}
public static MultipartFile convert(byte[] bytes, String fileName) {
// 创建 DiskFileItem 对象
DiskFileItem diskFileItem = new DiskFileItem("file", "application/octet-stream", false, fileName, (int) bytes.length, null);
try {
// 将字节数组写入 DiskFileItem
diskFileItem.getOutputStream().write(bytes);
diskFileItem.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
// 使用 CommonsMultipartFile 将 DiskFileItem 转换为 MultipartFile
return new CommonsMultipartFile(diskFileItem);
}
//类转换工具
public static Boolean isJsonValid(String warnResult, Class<?> clazz) {
ObjectMapper objectMapper = new ObjectMapper();
try {
objectMapper.readValue(warnResult, clazz);
return true;
} catch (JsonProcessingException e) {
return false;
}
}
}
下载历史视频
这里利用海康改的sdk下载
//下载录像文件至本地后上传至obs
byte[] doneStreamPost = CommUtil.doStreamPost(carmeraConfig.getHikHost(), carmeraConfig.getHikPort(), carmeraConfig.getHikUsername(), carmeraConfig.getHikPassword(), HikDataEnum.REQUEST_VIDEO_DOWNLOAD.getCode() + carmeraId, downLoadStr);
String warnResult = new String(doneStreamPost);
Boolean jsonValid = CommUtil.isJsonValid(warnResult, WarnResultError.class);
此时获得的doneStreamPost是ps封装的视频数据 (有可能会发生错误,此时返回的数据可能是错误信息,所以需要校验一下),这一点是一个很大的坑
然后进行视频转码,由于使用海康的转码的sdk需要签署一份协议,所以下面这个方法可以绕过签署协议(自己动手干吧),
下面这个方法没有使用真正的转码,而是通过视频录制的方式实现,性能一般;
public static byte[] videoConvert(byte[] bytes, WhGovernmentCarmera whGovernmentCarmera) throws IOException, InterruptedException {
// 写入临时文件
File file = File.createTempFile(whGovernmentCarmera.getChannelName(), HikDataEnum.VIDEO_FORMAT_MP4.getCode());
try (FileOutputStream outputStream = new FileOutputStream(file)) {
outputStream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
throw new IOException("Failed to write bytes to temporary file", e);
}
String absolutePath = file.getAbsolutePath();
String date2Time = DateUtils.date2Time(DateUtils.getNowDate());
File mp4File = new File(date2Time + HikDataEnum.VIDEO_FORMAT_MP4.getCode());
String mp4FileAbsolutePath = mp4File.getAbsolutePath();
FFmpegLogCallback.set();
Frame frame;
byte[] byteArray;
// 读取临时文件
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(absolutePath)) {
grabber.setFrameRate(25);
avutil.av_log_set_level(AV_LOG_ERROR);
grabber.setVideoCodec(avcodec.AV_CODEC_ID_H264);
grabber.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
grabber.setSampleRate(44100);
grabber.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
grabber.setTimestamp(30 * 1000 * 1000);
grabber.start();
try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(mp4FileAbsolutePath, grabber.getImageWidth(), grabber.getImageHeight())) {
recorder.setFormat("mp4");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setVideoQuality(28);
recorder.setVideoBitrate(2000000);
recorder.setFrameRate(25);
recorder.setSampleRate(44100);
recorder.setAudioChannels(2);
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.setVideoOption("preset", "slow");
recorder.setVideoOption("tune", "zerolatency");
recorder.setVideoOption("profile", "high");
recorder.setVideoOption("level", "3.1");
recorder.setOption("movflags", "faststart");
recorder.start();
// 从FrameGrabber中读取Frame并写入FrameRecorder中进行编码
while ((frame = grabber.grab()) != null) {
recorder.record(frame);
}
try {
if (Objects.nonNull(recorder)) {
recorder.stop();
recorder.release();
}
} catch (Exception exception) {
log.error("recorder告警视频处理异常,{}",exception.getCause());
}
try {
if (Objects.nonNull(grabber)) {
grabber.stop();
grabber.release();
}
} catch (Exception exception) {
log.error("grabber告警视频处理异常,{}",exception.getCause());
}
log.info("告警视频处理完成");
FileInputStream fileInputStream = new FileInputStream(mp4File);
byteArray = new byte[(int) mp4File.length()];
fileInputStream.read(byteArray);
fileInputStream.close();
// 清理临时文件
if (!file.delete()) {
file.deleteOnExit();
}
//清理
if (mp4File.exists()) {
mp4File.delete();
}
}
}catch (Exception e){
log.error("告警视频处理异常,{},将保存原视频信息",e.getCause());
//如果发生异常则保存原视频信息
byteArray=bytes;
}
return byteArray;
}
将得到的视频文件上传obs
// 将字节数组写入 .mp4 文件
String fileName = projectName + ULIDUtils.getULid() + HikDataEnum.VIDEO_FORMAT_MP4.getCode();
if (doneStreamPost.length > 0) {
R<SysFile> upload = remoteFileService.upload(CommUtil.convert(videoConvert2, fileName));
if (upload.getCode() == HttpStatus.SUCCESS) {
SysFile data = upload.getData();
url = data.getUrl();
}
log.info("设备:{},获取的告警视频url:{}", whGovernmentCarmera.getCarmeraId(), url);
}
以上是海康视频转码的主要流程
在部署的时候还遇到了一个问题就是ffmpeg始终找不到linux的动态链接库,由于使用的是javacv这个包(里面封装好了linux版本的动态链接库),经排查后发现是因为jdk版本不对,
javacv官方说是要使用 oracle jdk 或者openjdk ,ibmjdk,于是切换了jdk版本 这回不报错了;
更多推荐
所有评论(0)