RTP 推流

This commit is contained in:
shikong 2023-09-15 09:59:21 +08:00
parent afd9e47223
commit d40c98dc97
3 changed files with 121 additions and 33 deletions

View File

@ -118,6 +118,7 @@ public class InviteRequestProcessor implements MessageProcessor {
/**
* 模拟设备不支持实时 故直接回放 最近15分钟 当前时间录像
*
* @param gb28181Description gb28181 sdp
* @param mediaDescription 媒体描述符
*/
@ -131,6 +132,7 @@ public class InviteRequestProcessor implements MessageProcessor {
/**
* 模拟设备 录像回放 当前小时至当前时间录像
*
* @param gb28181Description gb28181 sdp
* @param mediaDescription 媒体描述符
*/
@ -154,7 +156,7 @@ public class InviteRequestProcessor implements MessageProcessor {
int port = media.getMediaPort();
log.info("目标端口号: {}", port);
deviceProxyService.proxyVideo2Rtp(device,start,stop);
deviceProxyService.proxyVideo2Rtp(device, start, stop, address, port);
// TODO 推流 && 关流事件订阅
}
}

View File

@ -1,5 +1,6 @@
package cn.skcks.docking.gb28181.mocking.core.sip.message.processor.message.request.recordinfo;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.skcks.docking.gb28181.common.xml.XmlUtils;
@ -20,7 +21,7 @@ import org.springframework.stereotype.Component;
import javax.sip.header.CallIdHeader;
import javax.sip.header.FromHeader;
import javax.sip.message.Response;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -57,6 +58,22 @@ public class RecordInfoRequestProcessor {
DateUtil.format(startTime, DatePattern.NORM_DATETIME_FORMATTER),
DateUtil.format(endTime, DatePattern.NORM_DATETIME_FORMATTER));
List<RecordInfoItemDTO> recordInfoItemDTOList = new ArrayList<>();
Date tmpStart = startTime;
Date tmpEnd = DateUtil.offsetMinute(tmpStart,5);
while(DateUtil.compare(tmpStart, endTime) < 0){
RecordInfoItemDTO recordInfoItemDTO = new RecordInfoItemDTO();
recordInfoItemDTO.setName(name);
recordInfoItemDTO.setStartTime(tmpStart);
recordInfoItemDTO.setEndTime(tmpEnd);
recordInfoItemDTO.setSecrecy(recordInfoRequestDTO.getSecrecy());
recordInfoItemDTO.setDeviceId(device.getGbChannelId());
recordInfoItemDTOList.add(recordInfoItemDTO);
tmpStart = tmpEnd;
tmpEnd = DateUtil.offsetMinute(tmpStart,5);
}
RecordInfoItemDTO recordInfoItemDTO = new RecordInfoItemDTO();
recordInfoItemDTO.setName(name);
recordInfoItemDTO.setStartTime(startTime);
@ -64,20 +81,21 @@ public class RecordInfoRequestProcessor {
recordInfoItemDTO.setSecrecy(recordInfoRequestDTO.getSecrecy());
recordInfoItemDTO.setDeviceId(device.getGbChannelId());
List<RecordInfoItemDTO> recordInfoItemDTOList = Collections.singletonList(recordInfoItemDTO);
FromHeader fromHeader = request.getFromHeader();
ListUtil.partition(recordInfoItemDTOList,2).forEach(recordList->{
RecordInfoResponseDTO recordInfoResponseDTO = new RecordInfoResponseDTO();
recordInfoResponseDTO.setSn(recordInfoRequestDTO.getSn());
recordInfoResponseDTO.setDeviceId(device.getGbChannelId());
recordInfoResponseDTO.setName(device.getName());
recordInfoResponseDTO.setSumNum((long) recordInfoItemDTOList.size());
recordInfoResponseDTO.setRecordList(recordInfoItemDTOList);
recordInfoResponseDTO.setRecordList(recordList);
FromHeader fromHeader = request.getFromHeader();
sender.sendRequest((provider, ip, port) -> {
CallIdHeader callIdHeader = provider.getNewCallId();
return SipRequestBuilder.createMessageRequest(device,
ip, port, 1, XmlUtils.toXml(recordInfoResponseDTO), fromHeader.getTag(), callIdHeader);
});
});
}
private SipSender.SendResponse ok(SIPRequest request) {

View File

@ -1,18 +1,30 @@
package cn.skcks.docking.gb28181.mocking.service.device;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.URLUtil;
import cn.skcks.docking.gb28181.mocking.config.sip.DeviceProxyConfig;
import cn.skcks.docking.gb28181.mocking.orm.mybatis.dynamic.model.MockingDevice;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
@Service
@ -22,15 +34,71 @@ public class DeviceProxyService {
private final DeviceProxyConfig proxyConfig;
public void proxyVideo2Rtp(MockingDevice device, Date startTime, Date endTime){
String url = URLUtil.completeUrl(proxyConfig.getUrl(), "/video");
public void proxyVideo2Rtp(MockingDevice device, Date startTime, Date endTime, String rtpAddr, int rtpPort){
String fromUrl = URLUtil.completeUrl(proxyConfig.getUrl(), "/video");
HashMap<String, String> map = new HashMap<>(3);
String deviceCode = device.getDeviceCode();
map.put("device_id", deviceCode);
map.put("begin_time", DateUtil.format(startTime, DatePattern.PURE_DATETIME_FORMAT));
map.put("end_time", DateUtil.format(endTime, DatePattern.PURE_DATETIME_FORMAT));
String query = URLUtil.buildQuery(map, StandardCharsets.UTF_8);
url = StringUtils.joinWith("?",url,query);
log.info("设备: {} 视频 url: {}", deviceCode, url);
fromUrl = StringUtils.joinWith("?", fromUrl, query);
log.info("设备: {} 视频 url: {}", deviceCode, fromUrl);
rtpAddr = "192.168.1.241";
String toUrl = StringUtils.joinWith("", "rtp://", rtpAddr, ":", rtpPort);
long time = DateUtil.between(startTime, endTime, DateUnit.SECOND);
pushRtp(fromUrl, toUrl, time);
}
@SneakyThrows
public void pushRtp(String fromUrl, String toUrl, long time) {
log.info("创建推流任务 fromUrl {}, toUrl {}, time: {}", fromUrl, toUrl, time);
// FFmpeg 调试日志
// FFmpegLogCallback.set();
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(fromUrl);
grabber.start();
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(toUrl, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setInterleaved(true);
recorder.setVideoOption("preset", "ultrafast");
recorder.setVideoOption("tune", "zerolatency");
recorder.setVideoOption("crf", "25");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setSampleRate(grabber.getSampleRate());
recorder.setOption("flvflags", "no_duration_filesize");
recorder.setOption("movflags","frag_keyframe+empty_moov");
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioBitrate(grabber.getAudioBitrate());
recorder.setAudioCodec(grabber.getAudioCodec());
}
recorder.setVideoBitrate(grabber.getVideoBitrate());
// recorder.setVideoCodec(grabber.getVideoCodec());
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); // 视频源数据yuv
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); // 设置音频压缩方式
recorder.setFormat("rtp_mpegts");
recorder.setVideoOption("threads", String.valueOf(Runtime.getRuntime().availableProcessors())); // 解码线程数
recorder.start(grabber.getFormatContext());
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
AtomicBoolean record = new AtomicBoolean(true);
scheduledExecutorService.schedule(() -> {
log.info("到达结束时间, 结束推送 fromUrl: {}, toUrl: {}", fromUrl, toUrl);
record.set(false);
}, time, TimeUnit.SECONDS);
try {
AVPacket k;
while (record.get() && (k = grabber.grabPacket()) != null) {
recorder.recordPacket(k);
avcodec.av_packet_unref(k);
}
grabber.close();
recorder.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
log.info("结束推送 fromUrl: {}, toUrl: {}", fromUrl, toUrl);
}
}