Когда будем стыковать и развивать аудио и видео модуль платформы Android,Столкнулся с такой проблемой,Производители надеются получить потоки RTSP от Hikvision, Dahua и других камер,Затем им возвращаются декодированные данные YUV или RGB.,После того, как они проведут анализ или обработку видео,Затем доставьте его в облегченный сервисный модуль RTSP или push-модуль RTMP.,Реализация обработанных данных,Вторичная переадресация,Эта статья предназначена для получения потока RTSP.,Возьмем пример внедрения облегченного сервиса RTSP после анализа.,Позвольте мне представить общий смысл термина «Техническая поставка».
Без лишних слов, без изображений нет истины. Изображение ниже показано во время тестирования. Терминал Android извлекает поток RTSP, затем вызывает обратно данные YUV и вводит их в облегченную службу RTSP через интерфейс push, а затем в Windows. платформа извлекает легкий URL-адрес RTSP, общая задержка в миллисекундах:
Давайте сначала поговорим о получении потока RTSP. Следует отметить, что если вы не хотите его воспроизводить, вы можете установить для второго параметра значение null при вызове SetSurface(). Если вам не нужен звук, просто установите для SetMute значение 1. , потому что вам нужно выполнить обратный вызов YUV. Затем установите обратный вызов I420. Если вам нужен RGB, просто включите обратный вызов RGB.
private boolean StartPlay()
{
if (!OpenPullHandle())
return false;
// Если для второго параметра установлено значение null, воспроизводится чистый звук.
libPlayer.SmartPlayerSetSurface(playerHandle, sSurfaceView);
libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1);
// libPlayer.SmartPlayerSetExternalRender(playerHandle, new
// RGBAExternalRender());
libPlayer.SmartPlayerSetExternalRender(playerHandle, new
I420ExternalRender());
libPlayer.SmartPlayerSetFastStartup(playerHandle, isFastStartup ? 1 : 0);
libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1);
if (isMute) {
libPlayer.SmartPlayerSetMute(playerHandle, isMute ? 1
: 0);
}
if (isHardwareDecoder)
{
int isSupportH264HwDecoder = libPlayer
.SetSmartPlayerVideoHWDecoder(playerHandle, 1);
int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle, 1);
Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
}
libPlayer.SmartPlayerSetLowLatencyMode(playerHandle, isLowLatency ? 1
: 0);
libPlayer.SmartPlayerSetRotation(playerHandle, rotate_degrees);
int iPlaybackRet = libPlayer
.SmartPlayerStartPlay(playerHandle);
if (iPlaybackRet != 0) {
Log.e(TAG, "StartPlay failed!");
if ( !isPulling && !isRecording && !isPushing && !isRTSPPublisherRunning)
{
releasePlayerHandle();
}
return false;
}
isPlaying = true;
return true;
}
Соответствующая реализация OpenPullHandle() выглядит следующим образом:
/*
* SmartRelayDemo.java
* Created: daniusdk.com
*/
private boolean OpenPullHandle()
{
//if (playerHandle != 0) {
// return true;
//}
if(isPulling || isPlaying || isRecording)
return true;
//playbackUrl = "rtsp://xxxx";
if (playbackUrl == null) {
Log.e(TAG, "playback URL is null...");
return false;
}
playerHandle = libPlayer.SmartPlayerOpen(myContext);
if (playerHandle == 0) {
Log.e(TAG, "playerHandle is nil..");
return false;
}
libPlayer.SetSmartPlayerEventCallbackV2(playerHandle,
new EventHandlePlayerV2());
libPlayer.SmartPlayerSetBuffer(playerHandle, playBuffer);
// set report download speed
libPlayer.SmartPlayerSetReportDownloadSpeed(playerHandle, 1, 5);
//Устанавливаем тайм-аут RTSP
int rtsp_timeout = 12;
libPlayer.SmartPlayerSetRTSPTimeout(playerHandle, rtsp_timeout);
//Устанавливаем RTSP Автоматическое переключение режима TCP/UDP
int is_auto_switch_tcp_udp = 1;
libPlayer.SmartPlayerSetRTSPAutoSwitchTcpUdp(playerHandle, is_auto_switch_tcp_udp);
libPlayer.SmartPlayerSaveImageFlag(playerHandle, 1);
// It only used when playback RTSP stream..
//libPlayer.SmartPlayerSetRTSPTcpMode(playerHandle, 1);
libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl);
return true;
}
Статус обратного вызова события на стороне потоковой передачи следующий. На стороне потоковой передачи основное внимание уделяется состоянию канала и скорости загрузки в реальном времени:
class EventHandlePlayerV2 implements NTSmartEventCallbackV2 {
@Override
public void onNTSmartEventCallbackV2(long handle, int id, long param1,
long param2, String param3, String param4, Object param5) {
//Log.i(TAG, "EventHandleV2: handle=" + handle + " id:" + id);
String player_event = "";
switch (id) {
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
player_event = "начинать..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
player_event = «Соединяемся..»;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
player_event = «Соединение не удалось.»;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
player_event = «Соединение успешно..»;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
player_event = «Соединение прервано.»;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
player_event = «Хватит играть..»;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
player_event = «Информация о разрешении: width: " + param1 + ", height: " + param2;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
player_event = «Невозможно получить медиаданные, возможно, URL-адрес неправильный.»;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
player_event = "Переключить URL воспроизведения..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
player_event = «Снимок: " + param1 + " путь:" + param3;
if (param1 == 0) {
player_event = player_event + ", Снимок сделан успешно";
} else {
player_event = player_event + ", Не удалось сделать снимок";
}
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
player_event = «[запись] запускает новый видеофайл : " + param3;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
player_event = «[запись] создала видеофайл : " + param3;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
Log.i(TAG, "Start Buffering");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
Log.i(TAG, "Buffering:" + param1 + "%");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
Log.i(TAG, "Stop Buffering");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
player_event = "download_speed:" + param1 + "Byte/s" + ", "
+ (param1 * 8 / 1000) + "kbps" + ", " + (param1 / 1024)
+ "KB/s";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RTSP_STATUS_CODE:
Log.e(TAG, "RTSP error code received, please make sure username/password is correct, error code:" + param1);
player_event = "RTSP error code:" + param1;
break;
}
}
}
Следующий шаг — запуск службы RTSP:
//Запуск/остановка службы RTSP
class ButtonRtspServiceListener implements OnClickListener {
public void onClick(View v) {
if (isRTSPServiceRunning) {
stopRtspService();
btnRtspService.setText("Запустить службу RTSP");
btnRtspPublisher.setEnabled(false);
isRTSPServiceRunning = false;
return;
}
if(!OpenPushHandle())
{
return;
}
Log.i(TAG, "onClick start rtsp service..");
rtsp_handle_ = libPublisher.OpenRtspServer(0);
if (rtsp_handle_ == 0) {
Log.e(TAG, «Создать rtsp экземпляр сервера не выполнен! Пожалуйста, проверьте достоверность SDK");
} else {
int port = 8554;
if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {
libPublisher.CloseRtspServer(rtsp_handle_);
rtsp_handle_ = 0;
Log.e(TAG, «Создать rtsp Порт сервера не удался! Пожалуйста, проверьте, не дублируется ли порт или порт находится вне диапазона!");
}
//String user_name = "admin";
//String password = "12345";
//libPublisher.SetRtspServerUserNamePassword(rtsp_handle_, user_name, password);
if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {
Log.i(TAG, "Запустить ртсп server успех!");
} else {
libPublisher.CloseRtspServer(rtsp_handle_);
rtsp_handle_ = 0;
Log.e(TAG, "Запустить ртсп сервер вышел из строя! Пожалуйста, проверьте, занят ли установленный порт!");
}
btnRtspService.setText("Остановить службу RTSP");
btnRtspPublisher.setEnabled(true);
isRTSPServiceRunning = true;
}
}
}
Если вам нужно остановить службу, соответствующая реализация выглядит следующим образом:
//Остановка службы RTSP
private void stopRtspService() {
if(!isRTSPServiceRunning)
return;
if (libPublisher != null && rtsp_handle_ != 0) {
libPublisher.StopRtspServer(rtsp_handle_);
libPublisher.CloseRtspServer(rtsp_handle_);
rtsp_handle_ = 0;
}
if(!isPushing)
{
releasePublisherHandle();
}
isRTSPServiceRunning = false;
}
Опубликовать и прекратить публикацию RTSP-потоков:
private boolean StartRtspStream()
{
if (isRTSPPublisherRunning)
return false;
String rtsp_stream_name = "stream1";
libPublisher.SetRtspStreamName(publisherHandle, rtsp_stream_name);
libPublisher.ClearRtspStreamServer(publisherHandle);
libPublisher.AddRtspStreamServer(publisherHandle, rtsp_handle_, 0);
if (libPublisher.StartRtspStream(publisherHandle, 0) != 0)
{
Log.e(TAG, «Ошибка публикации интерфейса потока rtsp!»);
if (!isPushing)
{
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
return false;
}
isRTSPPublisherRunning = true;
return true;
}
//Остановить публикацию RTSP-потока
private void stopRtspPublisher()
{
if(!isRTSPPublisherRunning)
return;
isRTSPPublisherRunning = false;
if (null == libPublisher || 0 == publisherHandle)
return;
libPublisher.StopRtspStream(publisherHandle);
if (!isPushing && !isRTSPServiceRunning)
{
releasePublisherHandle();
}
}
Поскольку данные YUV или RGB необходимо перекодировать после обработки, в это время необходимо установить конец push-уведомления для установки параметров кодирования:
private boolean OpenPushHandle() {
if(publisherHandle != 0)
{
return true;
}
publisherHandle = libPublisher.SmartPublisherOpen(myContext, audio_opt, video_opt,
videoWidth, videoHeight);
if (publisherHandle == 0) {
Log.e(TAG, "sdk open failed!");
return false;
}
Log.i(TAG, "publisherHandle=" + publisherHandle);
int fps = 20;
int gop = fps * 1;
int videoEncodeType = 1; //1: h.264 жестко закодирован 2: H.265 жестко запрограммирован
if(videoEncodeType == 1) {
int h264HWKbps = setHardwareEncoderKbps(true, videoWidth, videoHeight);
h264HWKbps = h264HWKbps*fps/25;
Log.i(TAG, "h264HWKbps: " + h264HWKbps);
int isSupportH264HWEncoder = libPublisher
.SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps);
if (isSupportH264HWEncoder == 0) {
libPublisher.SetNativeMediaNDK(publisherHandle, 0);
libPublisher.SetVideoHWEncoderBitrateMode(publisherHandle, 1); // 0:CQ, 1:VBR, 2:CBR
libPublisher.SetVideoHWEncoderQuality(publisherHandle, 39);
libPublisher.SetAVCHWEncoderProfile(publisherHandle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High
// libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x200); // Level 3.1
// libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x400); // Level 3.2
// libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x800); // Level 4
libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x1000); // Level 4.1 В большинстве случаев этого достаточно
//libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x2000); // Level 4.2
// libPublisher.SetVideoHWEncoderMaxBitrate(publisherHandle, ((long)h264HWKbps)*1300);
Log.i(TAG, "Great, it supports h.264 hardware encoder!");
}
}
else if (videoEncodeType == 2) {
int hevcHWKbps = setHardwareEncoderKbps(false, videoWidth, videoHeight);
hevcHWKbps = hevcHWKbps*fps/25;
Log.i(TAG, "hevcHWKbps: " + hevcHWKbps);
int isSupportHevcHWEncoder = libPublisher
.SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps);
if (isSupportHevcHWEncoder == 0) {
libPublisher.SetNativeMediaNDK(publisherHandle, 0);
libPublisher.SetVideoHWEncoderBitrateMode(publisherHandle, 0); // 0:CQ, 1:VBR, 2:CBR
libPublisher.SetVideoHWEncoderQuality(publisherHandle, 39);
// libPublisher.SetVideoHWEncoderMaxBitrate(publisherHandle, ((long)hevcHWKbps)*1200);
Log.i(TAG, "Great, it supports hevc hardware encoder!");
}
}
libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandlePublisherV2());
libPublisher.SmartPublisherSetGopInterval(publisherHandle, gop);
libPublisher.SmartPublisherSetFPS(publisherHandle, fps);
return true;
}
Реализация I420ExternalRender выглядит следующим образом. Здесь вы можете получить данные YUV потока RTSP, а затем после обработки вы можете вызвать PostLayerImageI420ByteBuffer() push-конца, чтобы доставить его в облегченную службу RTSP или push-конец RTMP для кодирования и отправьте его.
class I420ExternalRender implements NTExternalRender {
// public static final int NT_FRAME_FORMAT_RGBA = 1;
// public static final int NT_FRAME_FORMAT_ABGR = 2;
// public static final int NT_FRAME_FORMAT_I420 = 3;
private int width_ = 0;
private int height_ = 0;
private int y_row_bytes_ = 0;
private int u_row_bytes_ = 0;
private int v_row_bytes_ = 0;
private ByteBuffer y_buffer_ = null;
private ByteBuffer u_buffer_ = null;
private ByteBuffer v_buffer_ = null;
@Override
public int getNTFrameFormat() {
Log.i(TAG, "I420ExternalRender::getNTFrameFormat return "
+ NT_FRAME_FORMAT_I420);
return NT_FRAME_FORMAT_I420;
}
@Override
public void onNTFrameSizeChanged(int width, int height) {
width_ = width;
height_ = height;
y_row_bytes_ = (width_ + 15) & (~15);
u_row_bytes_ = ((width_ + 1) / 2 + 15) & (~15);
v_row_bytes_ = ((width_ + 1) / 2 + 15) & (~15);
y_buffer_ = ByteBuffer.allocateDirect(y_row_bytes_ * height_);
u_buffer_ = ByteBuffer.allocateDirect(u_row_bytes_
* ((height_ + 1) / 2));
v_buffer_ = ByteBuffer.allocateDirect(v_row_bytes_
* ((height_ + 1) / 2));
Log.i(TAG, "I420ExternalRender::onNTFrameSizeChanged width_="
+ width_ + " height_=" + height_ + " y_row_bytes_="
+ y_row_bytes_ + " u_row_bytes_=" + u_row_bytes_
+ " v_row_bytes_=" + v_row_bytes_);
}
@Override
public ByteBuffer getNTPlaneByteBuffer(int index) {
if (index == 0) {
return y_buffer_;
} else if (index == 1) {
return u_buffer_;
} else if (index == 2) {
return v_buffer_;
} else {
Log.e(TAG, "I420ExternalRender::getNTPlaneByteBuffer index error:" + index);
return null;
}
}
@Override
public int getNTPlanePerRowBytes(int index) {
if (index == 0) {
return y_row_bytes_;
} else if (index == 1) {
return u_row_bytes_;
} else if (index == 2) {
return v_row_bytes_;
} else {
Log.e(TAG, "I420ExternalRender::getNTPlanePerRowBytes index error:" + index);
return 0;
}
}
public void onNTRenderFrame(int width, int height, long timestamp)
{
if ( y_buffer_ == null )
return;
if ( u_buffer_ == null )
return;
if ( v_buffer_ == null )
return;
y_buffer_.rewind();
u_buffer_.rewind();
v_buffer_.rewind();
if( isPushing || isRTSPPublisherRunning )
{
libPublisher.PostLayerImageI420ByteBuffer(publisherHandle, 0, 0, 0,
y_buffer_, 0, y_row_bytes_,
u_buffer_, 0, u_row_bytes_,
v_buffer_, 0, v_row_bytes_,
width_, height_, 0, 0,
960, 540, 0,0);
}
}
}
Если облегченная служба запускается нормально, URL-адрес rtsp будет вызван обратно:
class EventHandlePublisherV2 implements NTSmartEventCallbackV2 {
@Override
public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {
Log.i(TAG, "EventHandlePublisherV2: handle=" + handle + " id:" + id);
String publisher_event = "";
switch (id) {
....
case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
publisher_event = «URL-адрес службы RTSP: " + param3;
break;
}
}
}
Вышеупомянутый процесс представляет собой грубый процесс. После передачи потока из RTSP для обработки данных он затем отправляется в упрощенную службу RTSP, а затем проигрыватель извлекает поток из облегченного сервера RTSP, если задержка обработки для алгоритма YUV или RGB. невелика. Общая задержка может легко достигать уровня миллисекунд, что соответствует техническим требованиям большинства сценариев.