Android平台RTMP直播推送模块技术接入说明

news2024/9/20 20:50:35

技术背景

大牛直播SDK跨平台RTMP直播推送模块,始于2015年,支持Windows、Linux(x64_64架构|aarch64)、Android、iOS平台,支持采集推送摄像头、屏幕、麦克风、扬声器、编码前、编码后数据对接,功能强大,性能优异,配合大牛直播SDK的SmartPlayer播放器,轻松实现毫秒级的延迟体验,满足大多数行业的使用场景。

RTMP直播推送模块数据源,支持编码前、编码后数据对接:

  • 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型);
  • 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据)。

技术对接

 系统要求

  • SDK支持Android5.1及以上版本;
  • 支持的CPU架构:armv7, arm64, x86, x86_64。

准备工作

  • 确保SmartPublisherJniV2.java放到com.daniulive.smartpublisher包名下(可在其他包名下调用);
  • smartavengine.jar加入到工程;
  • 拷贝libSmartPublisher.so到工程;
  • AndroidManifast.xml添加相关权限:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
  • Load相关so:
static {  
    System.loadLibrary("SmartPublisher");
}
  • build.gradle配置32/64位库:
splits {
    abi {
        enable true
        reset()
        // Specifies a list of ABIs that Gradle should create APKs for
        include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' //select ABIs to build APKs for
        // Specify that we do not want to also generate a universal APK that includes all ABIs
        universalApk true
    }
}
  • 如需集成到自己系统测试,请用大牛直播SDK的app name,授权版按照授权app name正常使用即可;
  • 如何改app-name,strings.xml做以下修改:
<string name="app_name">SmartPublisherSDKDemo</string>

接口设计

Android 推送端SDK接口详解

调用描述

接口

接口描述

最先调用,如成功返回推送实例

SmartPublisherOpen

ctx:上下文信息;

Audio_opt:

0:不推送音频;

1:推送编码前音频(PCM);

2:对接外部编码后的audio数据(AAC/PCMA/PCMU/SPEEX)

video_opt:

0:不推送视频;

1:推送编码前视频(YUV420SP/YUV420P/RGBA/ARGB);

2:推送编码后视频(H.264)

3:层叠加模式

width|height:宽高信息。

Event回调

SetSmartPublisherEventCallbackV2

设置event callback

硬编码设置

SetSmartPublisherVideoHWEncoder

检测是否支持H.264硬编码,如果返回0,则支持,否则自动采用软编码

SetSmartPublisherVideoHevcHWEncoder

检测是否支持H.265(HEVC)硬编码,如果返回0,则支持,否则自动采用软编码

SetNativeMediaNDK

设置视频硬编码是否使用 Native Media NDK, 默认是不使用, 安卓5.0以下设备不支持

SetVideoHWEncoderBitrateMode

设置视频硬编码码率控制模式

hw_bitrate_mode: -1表示使用默认值, 不设置也会使用默认值, 0:CQ, 1:VBR, 2:CBR, 3:CBR_FD

SetVideoHWEncoderComplexity

设置视频硬编码复杂度, 安卓5.0及以上支持

SetVideoHWEncoderQuality

设置视频硬编码质量, 安卓9及以上支持, 仅当硬编码器码率控制模式(BitrateMode)是CQ(constant-quality mode)时才有效

SetAVCHWEncoderProfile

设置H.264硬编码Profile, 安卓7及以上支持

SetAVCHWEncoderLevel

设置H.264硬编码Level, 这个只有在设置了Profile的情况下才有效, 安卓7及以上支持

SetVideoHWEncoderMaxBitrate

设置视频硬编码最大码率, 安卓没有相关文档说明, 所以不建议设置

水印

文字、png水印

PostLayerBitmap

通过层模式设置水印,投递层

Bitmap.Config.ARGB_888图像

视频参数配置

软编码可变码率

SmartPublisherSetSwVBRMode

设置软编码可变码率,可变码率下,相邻帧之间变化不大时码率更低

GOP间隔(关键帧)

SmartPublisherSetGopInterval

设置推送端GOP间隔,一般建议在帧率的1~3倍,如不设置,用底层默认值

软编码码率设置

SmartPublisherSetSWVideoBitRate

设置软编码视频 bit-rate,最大码流一般是平均码流的2倍,如不设置,用底层计算的默认值

帧率

SmartPublisherSetFPS

设置fps,如不设置,用底层默认值

软编码视频Profile

SmartPublisherSetSWVideoEncoderProfile

设置软编码模式下的video encoder profile,默认baseline profile

软编码编码速度

SmartPublisherSetSWVideoEncoderSpeed

设置软编码编码速度,设置范围(1,6),1最快,6最慢,默认是6

频设置

视频镜像

SmartPublisherSetMirror

镜像模式: 播放端和推送端本地回显方向显示一致(前置摄像头)

视频截图

实时快照

CaptureImage

截图接口, 支持JPEG和PNG两种格式

音频配置

音频编码

类型

SmartPublisherSetAudioCodecType

设置编码类型,默认AAC编码,type设置为2时,启用speex编码(码率更低)

AAC编码码率

SmartPublisherSetAudioBitRate

设置音频编码码率, 当前只对AAC编码有效

SPEEX编码质量

SmartPublisherSetSpeexEncoderQuality

设置speex编码质量,数值越大,质量越高,范围(0,10),默认8

音频处理

噪音抑制

SmartPublisherSetNoiseSuppression

噪音抑制开启后,可去除采集端背景杂音

增益控制

SmartPublisherSetAGC

设置自动增益控制,保持声音稳定

回声消除

SmartPublisherSetEchoCancellation

设置音频回音消除

实时静音

SmartPublisherSetMute

设置实时静音、取消静音

设置输入

音量

SmartPublisherSetInputAudioVolume

设置输入音量,默认是1.0,范围是[0.0, 5.0], 设置成0静音, 1音量不变

RTMP推送模式

SetRtmpPublishingType

设置rtmp publisher类型,0:live,1:record,需服务器支持

Enhanced RTMP设置

DisableEnhancedRTMP

disable enhanced RTMP, SDK默认是开启enhanced RTMP的

RTMP推送URL设置

SmartPublisherSetURL

设置RTMP推送url

编码前实时视频数据

camera数据

SmartPublisherOnCaptureVideoData

对接camera回调的数据

YV12数据

SmartPublisherOnYV12Data

YV12数据接口

NV21数据

SmartPublisherOnNV21Data

NV21数据接口

转换接口

SmartPublisherNV21ToI420Rotate

NV21转换到I420并旋转

YUV(I420)

SmartPublisherOnCaptureVideoI420Data

第三方YUV(I420)接口

RGB24数据

SmartPublisherOnCaptureVideoRGB24Data

RGB24接口

RGBA32数据

SmartPublisherOnCaptureVideoRGBA32Data

RGBA32接口

YUV420888数据

SmartPublisherOnImageYUV420888

YUV420888接口

RGBA数据

SmartPublisherOnCaptureVideoRGBAData

第三方RGBA数据

ABGR垂直翻转数据

SmartPublisherOnCaptureVideoABGRFlip

VerticalData

ABGR flip vertical(垂直翻转) 数据(Demo中用于传递屏幕数据)

RGBA8888图像

PostLayerImageRGBA8888ByteBuffer

投递层RGBA8888图像,如果不需要Aplpha通道的话, 请使用RGBX8888接口

RGBX8888图像

PostLayerImageRGBX8888ByteBuffer

投递层RGBX8888图像

I420图像

PostLayerImageI420ByteBuffer

投递层I420图像

RGB565数据

SmartPublisherOnCaptureVideoRGB565Data

RGB565 data

裁剪过的RGBA

数据

SmartPublisherOnCaptureVideoClipedRGBAData

投递裁剪过的RGBA数据

PCM数据

SmartPublisherOnPCMData

实时PCM数据

远端PCM数据

(用于回音消除)

SmartPublisherOnFarEndPCMData

实时传递远端PCM数据(可用于互动级的回音消除处理)

音频 混音

混音数据

SmartPublisherOnMixPCMData

传递PCM混音音频数据给SDK, 每10ms音频数据传入一次

编码后数据对接

编码后视频数据

SmartPublisherPostVideoEncodedData

设置编码后视频数据

编码后音频数据

SmartPublisherPostAudioEncodedData

编码后音频数据

编码后音视频数据回调

编码后音频数据回调

SmartPublisherSetAudioEncodedDataCallback

设置编码后音频数据回调

编码后视频数据回调

SmartPublisherSetVideoEncodedDataCallback

设置编码后视频数据回调

层结构设置

启用|停用视频层

EnableLayer

video_opt为3时,启用或者停用视频层, 这个接口必须在StartXXX之后调用.

移除视频层

RemoveLayer

移除视频层, 这个接口必须在StartXXX之后调用.

RTMP推送

开始推送

RTMP

SmartPublisherStartPublisher

启动RTMP推送

停止推送

RTMP

SmartPublisherStopPublisher

停止RTMP推送

关闭推送实例

关闭实例

SmartPublisherClose

关闭推送实例,结束时必须调用close接口释放资源

设置授权

授权license设置

SmartPublisherSetSDKClientKey

设置授权Key,如需设置授权Key, 请确保在SmartPublisherOpen之前调用!

功能支持

  • 音频编码:AAC/SPEEX;
  • 视频编码:H.264、H.265;
  • 推流协议:RTMP;
  • [音视频]支持纯音频/纯视频/音视频推送;
  • [摄像头]支持采集过程中,前后摄像头实时切换;
  • 支持帧率、关键帧间隔(GOP)、码率(bit-rate)设置;
  • 支持RTMP推送 live|record模式设置;
  • 支持前置摄像头镜像设置;
  • 支持软编码、特定机型硬编码;
  • 支持横屏、竖屏推送;
  • 支持Android屏幕采集推送;
  • 支持自建标准RTMP服务器或CDN;
  • 支持断网自动重连、网络状态回调;
  • 支持实时动态水印;
  • 支持实时快照;
  • 支持降噪处理、自动增益控制;
  • 支持外部编码前音视频数据对接;
  • 支持外部编码后音视频数据对接;
  • 支持RTMP扩展H.265(需设备支持H.265特定机型硬编码)和Enhanced RTMP;
  • 支持实时音量调节;
  • 支持扩展录像模块;
  • 支持Unity接口;
  • 支持H.264扩展SEI发送模块;
  • 支持Android 5.1及以上版本。

接口调用详解

本文以大牛直播SDK Android平台Camera2Demo为例,推送RTMP之前,可以先选择视频分辨率、软编还是硬编码,音频是AAC、SPEEX还是PCMA编码等基础设置,其他参数的设置,可以参考下面InitAndSetConfig()。

以Android平台Camera2对接为例,onCreate()时,想new SmartPublisherJniV2():

/*
 * MainActivity.java
 * Author: daniusdk.com
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	
	...

	context_ = this.getApplicationContext();
	
	libPublisher = new SmartPublisherJniV2();
}

推送RTMP:

class ButtonStartPushListener implements View.OnClickListener {
	public void onClick(View v) {
		if (stream_publisher_.is_rtmp_publishing()) {
			stopPush();

			btnRTMPPusher.setText("推送RTMP");
			return;
		}

		Log.i(TAG, "onClick start push rtmp..");
		
        InitAndSetConfig();

		String rtmp_pusher_url ="rtmp://192.168.0.101:1935/hls/stream123";;


		if (!stream_publisher_.SetURL(rtmp_pusher_url))
			Log.e(TAG, "Failed to set publish stream URL..");

		boolean start_ret = stream_publisher_.StartPublisher();
		if (!start_ret) {
			stream_publisher_.try_release();
			Log.e(TAG, "Failed to start push stream..");
			return;
		}

		startAudioRecorder();
		startLayerPostThread();

		btnRTMPPusher.setText("停止推送 ");

	}
}

stopPush()实现如下:

//停止rtmp推送
private void stopPush() {
	stream_publisher_.StopPublisher();
	stream_publisher_.try_release();

	if (!stream_publisher_.is_publishing())
		stopAudioRecorder();
}

其中,InitAndSetConfig()实现如下,通过调SmartPublisherOpen()接口,生成推送实例句柄。

/*
 * MainActivity.java
 * Author: daniusdk.com
 */
private void InitAndSetConfig() {
	if (null == libPublisher)
		return;

	if (!stream_publisher_.empty())
		return;

	Log.i(TAG, "InitAndSetConfig video width: " + video_width_ + ", height" + video_height_ + " imageRotationDegree:" + cameraImageRotationDegree_);

	int audio_opt = 1;
	long handle = libPublisher.SmartPublisherOpen(context_, audio_opt, 3,  video_width_, video_height_);
	if (0==handle) {
		Log.e(TAG, "sdk open failed!");
		return;
	}

	Log.i(TAG, "publisherHandle=" + handle);

	int fps = 25;
	int gop = fps * 3;

	initialize_publisher(libPublisher, handle, video_width_, video_height_, fps, gop);

	stream_publisher_.set(libPublisher, handle);
}

对应的initialize_publisher()实现如下,设置软硬编码、帧率、关键帧间隔等。

private boolean initialize_publisher(SmartPublisherJniV2 lib_publisher, long handle, int width, int height, int fps, int gop) {
	if (null == lib_publisher) {
		Log.e(TAG, "initialize_publisher lib_publisher is null");
		return false;
	}

	if (0 == handle) {
		Log.e(TAG, "initialize_publisher handle is 0");
		return false;
	}

	if (videoEncodeType == 1) {
		int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, true);
		Log.i(TAG, "h264HWKbps: " + kbps);
		int isSupportH264HWEncoder = lib_publisher.SetSmartPublisherVideoHWEncoder(handle, kbps);
		if (isSupportH264HWEncoder == 0) {
			lib_publisher.SetNativeMediaNDK(handle, 0);
			lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBR
			lib_publisher.SetVideoHWEncoderQuality(handle, 39);
			lib_publisher.SetAVCHWEncoderProfile(handle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High

			// lib_publisher.SetAVCHWEncoderLevel(handle, 0x200); // Level 3.1
			// lib_publisher.SetAVCHWEncoderLevel(handle, 0x400); // Level 3.2
			// lib_publisher.SetAVCHWEncoderLevel(handle, 0x800); // Level 4
			lib_publisher.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1 多数情况下,这个够用了
			//lib_publisher.SetAVCHWEncoderLevel(handle, 0x2000); // Level 4.2

			// lib_publisher.SetVideoHWEncoderMaxBitrate(handle, ((long)h264HWKbps)*1300);

			Log.i(TAG, "Great, it supports h.264 hardware encoder!");
		}
	} else if (videoEncodeType == 2) {
		int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, false);
		Log.i(TAG, "hevcHWKbps: " + kbps);
		int isSupportHevcHWEncoder = lib_publisher.SetSmartPublisherVideoHevcHWEncoder(handle, kbps);
		if (isSupportHevcHWEncoder == 0) {
			lib_publisher.SetNativeMediaNDK(handle, 0);
			lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBR
			lib_publisher.SetVideoHWEncoderQuality(handle, 39);

			// libPublisher.SetVideoHWEncoderMaxBitrate(handle, ((long)hevcHWKbps)*1200);

			Log.i(TAG, "Great, it supports hevc hardware encoder!");
		}
	}

	boolean is_sw_vbr_mode = true;
	//H.264 software encoder
	if (is_sw_vbr_mode) {
		int is_enable_vbr = 1;
		int video_quality = LibPublisherWrapper.estimate_video_software_quality(width, height, true);
		int vbr_max_kbps = LibPublisherWrapper.estimate_video_vbr_max_kbps(width, height, fps);
		lib_publisher.SmartPublisherSetSwVBRMode(handle, is_enable_vbr, video_quality, vbr_max_kbps);
	}

	if (is_pcma_) {
		lib_publisher.SmartPublisherSetAudioCodecType(handle, 3);
	} else {
		lib_publisher.SmartPublisherSetAudioCodecType(handle, 1);
	}

	lib_publisher.SetSmartPublisherEventCallbackV2(handle, new EventHandlerPublisherV2().set(handler_, record_executor_));

	lib_publisher.SmartPublisherSetSWVideoEncoderProfile(handle, 3);

	lib_publisher.SmartPublisherSetSWVideoEncoderSpeed(handle, 2);

	lib_publisher.SmartPublisherSetGopInterval(handle, gop);

	lib_publisher.SmartPublisherSetFPS(handle, fps);

	// lib_publisher.SmartPublisherSetSWVideoBitRate(handle, 600, 1200);

	boolean is_noise_suppression = true;
	lib_publisher.SmartPublisherSetNoiseSuppression(handle, is_noise_suppression ? 1 : 0);

	boolean is_agc = false;
	lib_publisher.SmartPublisherSetAGC(handle, is_agc ? 1 : 0);

	int echo_cancel_delay = 0;
	lib_publisher.SmartPublisherSetEchoCancellation(handle, 1, echo_cancel_delay);

	return true;
}

数据投递如下(以Camera2采集为例,如果是其他视频格式,也可以正常对接):

@Override
public void onCameraImageData(Image image) {
	....
	for (LibPublisherWrapper i : publisher_array_)
		i.PostLayerImageYUV420888ByteBuffer(0, 0, 0,
			planes[0].getBuffer(), y_offset, planes[0].getRowStride(),
			planes[1].getBuffer(), u_offset, planes[1].getRowStride(),
			planes[2].getBuffer(), v_offset, planes[2].getRowStride(), planes[1].getPixelStride(),
			w, h, 0, 0,
			scale_w, scale_h, scale_filter_mode, rotation_degree);

}

音频采集投递设计如下:

void startAudioRecorder() {
	if (audio_recorder_ != null)
		return;

	audio_recorder_ = new NTAudioRecordV2(this);

	Log.i(TAG, "startAudioRecorder call audio_recorder_.start()+++...");

	audio_recorder_callback_ = new NTAudioRecordV2CallbackImpl(stream_publisher_, null);

	audio_recorder_.AddCallback(audio_recorder_callback_);

	if (!audio_recorder_.Start(is_pcma_ ? 8000 : 44100, 1) ) {
		audio_recorder_.RemoveCallback(audio_recorder_callback_);
		audio_recorder_callback_ = null;

		audio_recorder_ = null;

		Log.e(TAG, "startAudioRecorder start failed.");
	}
	else {
		Log.i(TAG, "startAudioRecorder call audio_recorder_.start() OK---...");
	}
}

void stopAudioRecorder() {
	if (null == audio_recorder_)
		return;

	Log.i(TAG, "stopAudioRecorder+++");

	audio_recorder_.Stop();

	if (audio_recorder_callback_ != null) {
		audio_recorder_.RemoveCallback(audio_recorder_callback_);
		audio_recorder_callback_ = null;
	}

	audio_recorder_ = null;

	Log.i(TAG, "stopAudioRecorder---");
}

回调Audio数据的地方,直接投递出去:

private static class NTAudioRecordV2CallbackImpl implements NTAudioRecordV2Callback {
	private WeakReference<LibPublisherWrapper> publisher_0_;

	public NTAudioRecordV2CallbackImpl(LibPublisherWrapper publisher_0) {
		if (publisher_0 != null)
			publisher_0_ = new WeakReference<>(publisher_0);
	}

	private final LibPublisherWrapper get_publisher_0() {
		if (publisher_0_ !=null)
			return publisher_0_.get();

		return null;
	}

	@Override
	public void onNTAudioRecordV2Frame(ByteBuffer data, int size, int sampleRate, int channel, int per_channel_sample_number) {

		LibPublisherWrapper publisher_0 = get_publisher_0();
		if (publisher_0 != null)
			publisher_0.OnPCMData(data, size, sampleRate, channel, per_channel_sample_number);
	}
}

图层投递设计如下,图层投递的时候,可设置是否添加文字、图片动态水印:

private void startLayerPostThread() {
	if (layer_post_thread_ != null)
		return;

	layer_post_thread_ = new LayerPostThread(this.context_, publisher_array_);
	layer_post_thread_.start_post();
	update_layer_post_video_size();
	layer_post_thread_.enableText(isHasTextWatermark());
	layer_post_thread_.enablePicture(isHasPictureWatermark());
}

private void update_layer_post_video_size() {
	if (null == layer_post_thread_)
		return;

	int w, h;
	int degree = cameraImageRotationDegree_;
	if (degree < 0 ) {
		w = 0;
		h = 0;
	} else if (90 == degree || 270 == degree) {
		w = video_height_;
		h = video_width_;
	}else {
		w = video_width_;
		h = video_height_;
	}

	layer_post_thread_.update_video_size(w, h);
}

private void stopLayerPostThread() {
	if (layer_post_thread_ != null) {
		layer_post_thread_.stop_post();
		layer_post_thread_ = null;
	}
}

如需摄像头快照,调用以下逻辑实现即可:

class ButtonCaptureImageListener implements View.OnClickListener {
	public void onClick(View v) {
		if (null == snap_shot_impl_) {
			snap_shot_impl_ = new SnapShotImpl(image_path_, context_, handler_, libPublisher, snap_shot_publisher_);
			snap_shot_impl_.start();
		}

		startLayerPostThread();
		snap_shot_impl_.set_layer_post_thread(layer_post_thread_);

		snap_shot_impl_.capture();
	}
}

如需集成录像模块,开始录像、停止录像设计如下:

class ButtonStartRecorderListener implements View.OnClickListener {
	public void onClick(View v) {
		if (layer_post_thread_ != null)
			layer_post_thread_.update_layers();

		if (stream_publisher_.is_recording()) {
			stopRecorder();

			if (stream_publisher_.empty())
				ConfigControlEnable(true);

			btnStartRecorder.setText("实时录像");
			btnPauseRecorder.setText("暂停录像");
			btnPauseRecorder.setEnabled(false);
			isPauseRecording = true;
			return;
		}

		Log.i(TAG, "onClick start recorder..");

		InitAndSetConfig();

		ConfigRecorderParam();

		boolean start_ret = stream_publisher_.StartRecorder();
		if (!start_ret) {
			stream_publisher_.try_release();
			Log.e(TAG, "Failed to start recorder.");
			return;
		}

		startAudioRecorder();
		ConfigControlEnable(false);

		startLayerPostThread();

		btnStartRecorder.setText("停止录像");
		btnPauseRecorder.setEnabled(true);
		isPauseRecording = true;
	}
}

录像参数配置实现如下:

void ConfigRecorderParam() {
	if (null == libPublisher)
		return;

	if (null == recDir || recDir.isEmpty())
		return;

	int ret = libPublisher.SmartPublisherCreateFileDirectory(recDir);
	if (ret != 0) {
		Log.e(TAG, "Create record dir failed, path:" + recDir);
		return;
	}

	if (!stream_publisher_.SetRecorderDirectory(recDir)) {
		Log.e(TAG, "Set record dir failed , path:" + recDir);
		return;
	}

	// 更细粒度控制录像的, 一般情况无需调用
	//libPublisher.SmartPublisherSetRecorderAudio(publisherHandle, 0);
	//libPublisher.SmartPublisherSetRecorderVideo(publisherHandle, 0);

	if (!stream_publisher_.SetRecorderFileMaxSize(200)) {
		Log.e(TAG, "SmartPublisherSetRecorderFileMaxSize failed.");
		return;
	}
}

暂停录像、恢复录像设计如下:

class ButtonPauseRecorderListener implements View.OnClickListener {
	public void onClick(View v) {
		if (stream_publisher_.is_recording()) {
			if (isPauseRecording) {
				boolean ret = stream_publisher_.PauseRecorder(true);
				if (ret) {
					isPauseRecording = false;
					btnPauseRecorder.setText("恢复录像");
				} else {
					Log.e(TAG, "Pause recorder failed..");
				}
			} else {
				boolean ret = stream_publisher_.PauseRecorder(false);
				if (ret) {
					isPauseRecording = true;
					btnPauseRecorder.setText("暂停录像");
				} else {
					Log.e(TAG, "Resume recorder failed..");
				}
			}
		}
	}
}

Event回调实现如下:

private static class EventHandlerPublisherV2 implements NTSmartEventCallbackV2 {
	@Override
	public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {

		Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);

		String publisher_event = "";

		switch (id) {
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STARTED:
				publisher_event = "开始..";
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTING:
				publisher_event = "连接中..";
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTION_FAILED:
				publisher_event = "连接失败..";
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTED:
				publisher_event = "连接成功..";
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_DISCONNECTED:
				publisher_event = "连接断开..";
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STOP:
				publisher_event = "关闭..";
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RECORDER_START_NEW_FILE:
				publisher_event = "开始一个新的录像文件 : " + param3;
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_ONE_RECORDER_FILE_FINISHED:
				if (record_executor_ != null) {
					RecordExecutorService executor = record_executor_.get();
					if (executor != null) {
						RecordFileFinishedHandler file_finished_handler = new RecordFileFinishedHandler().set(handle, param3, param1);
						if (param2 > 0)
							file_finished_handler.set_begin_time(param2);

						executor.execute(file_finished_handler);
					}
				}
				publisher_event = "已生成一个录像文件 : " + param3;
				break;

			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY:
				publisher_event = "发送时延: " + param1 + " 帧数:" + param2;
				break;

			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE:
				publisher_event = "快照: " + param1 + " 路径:" + param3;
				if (0 == param1)
					publisher_event = publisher_event + "截取快照成功.." + ", 用户数据:" + param4;
				 else
					publisher_event = publisher_event + "截取快照失败..";

				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
				publisher_event = "RTSP服务URL: " + param3;
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_RESPONSE_STATUS_CODE:
				publisher_event ="RTSP status code received, codeID: " + param1 + ", RTSP URL: " + param3;
				break;
			case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_NOT_SUPPORT:
				publisher_event ="服务器不支持RTSP推送, 推送的RTSP URL: " + param3;
				break;
		}

		String str = "当前回调状态:" + publisher_event;

		Log.i(TAG, str);

		if (handler_ != null) {
			android.os.Handler handler = handler_.get();
			if (handler != null) {
				Message message = new Message();
				message.what = PUBLISHER_EVENT_MSG;
				message.obj = publisher_event;
				handler.sendMessage(message);
			}
		}
	}

	public NTSmartEventCallbackV2 set(android.os.Handler handler, RecordExecutorService record_executor) {
		this.handler_ = new WeakReference<>(handler);
		this.record_executor_ = new WeakReference<>(record_executor);
		return this;
	}

	private WeakReference<android.os.Handler> handler_;
	private WeakReference<RecordExecutorService> record_executor_;
}

onDestroy() 的时候,调用stopPush()即可,如果有录像和快照,都停掉,此外,停掉图层投递线程,并关闭camera:

@Override
protected void onDestroy() {
	Log.i(TAG, "activity destory!");

	record_executor_.cancel_tasks();

	stopAudioRecorder();

	if (snap_shot_impl_ != null) {
		snap_shot_impl_.stop();
		snap_shot_impl_ = null;
	}

	snap_shot_publisher_.release();

	stopPush();
	stopRecorder();

	stream_publisher_.release();

	stopLayerPostThread();

	if (camera2Helper != null) {
		camera2Helper.release();
	}

	if (!record_executor_.shutdown(60, TimeUnit.SECONDS))
		Log.w(TAG, "call record_executor_.shutdown failed");

	super.onDestroy();
}

总结

以上是大牛直播SDK的Android平台RTMP直播推送模块详细的对接说明,除了可以对接编码前各种类型的音视频数据外,模块还支持对接编码后音视频数据,并实现本地录像、快照等功能,除支持H.264外,RTMP推送模块还支持扩展H.265和Enhanced RTMP。感兴趣的开发者,可以单独跟我们探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1990806.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XCode15.4真机运行调试

更新Xcode后&#xff0c;没有模拟器内容&#xff0c;而且真机也不显示&#xff0c;编译按钮无法点击&#xff0c;设备在管理运行目标中可见&#xff0c;但无法选中 解决方案&#xff1a;下载iOS17.5模拟器&#xff0c;但最坑的是直接点击“Get”下载总是中断&#xff0c;且无…

mysql幻读现象及其避免策略

mysql幻读现象及其避免策略 1、幻读是什么&#xff1f;2、快照读与当前读3、如何避免幻读&#xff1f;3.1 快照读3.2 当前读 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、幻读是什么&#xff1f; 幻读是事务中第二次查询返回了之前不…

Spring Boot 3.x gradle脚手架工程build.gradle详解

为了让读者轻松掌握gradle项目构建脚本中各种配置&#xff0c;我们将从0开始一点点启用配置&#xff0c;以做实验的尝试方式&#xff0c;让大家对各种配置的作用有比较深的印象。如果觉得对你有帮助&#xff0c;记得点赞收藏&#xff0c;关注小卷&#xff0c;后续更精彩&#x…

2024视频编辑网站微服务

文章目录 项目描述流量数据算法主站服务AIGC功能服务视频剪辑服务任务调度服务算法部署服务 项目描述 一款海外视频编辑工具&#xff0c;提供视频编辑、多媒体资源的AI处理、AIGC生成素材等功能。 流量数据 数据: 月活MAU(过去30天活跃用户数)为500万&#xff0c;20%的用户每…

跳蚤市场小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;管理员管理&#xff0c;商品信息管理&#xff0c;论坛管理&#xff0c;收货地址管理&#xff0c;基础数据管理&#xff0c;轮播图信息 微信端账号功能包括&#xff1a;系统首页&a…

windows 达梦到ORACLE dblink

达梦通过DBLINK访问Oracle数据库有两种: 方式一&#xff1a;通过Oracle oci接口; 方式二&#xff1a;一种是通过ODBC数据源的方式。 本案例选择使用Oralce OCI的方式去访问Oracle数据库。 配置Oracle OCI客户端 下载地址&#xff1a;https://www.oracle.com/database/techno…

提升人事工作效率,打造智慧校园人事管理系统

智慧校园人事管理系统中的“人事工作”功能是为了提高校园内人力资源管理的效率和规范化水平&#xff0c;确保教职工队伍的健康发展。这一功能涵盖了从面试管理、职工培训、职工考核、职工体检、职称评定到职工关怀等多个方面&#xff0c;旨在全方位支持教职工的职业发展和个人…

没有mac电脑ios上架截屏截图的最新方法

很多人使用uniapp或其他跨平台框架开发ios的app&#xff0c;上架的时候都会遇到一个问题&#xff0c;上架的时候需要各种尺寸的设备来做ios截屏&#xff0c;比如目前最新的要求是&#xff0c;需要对6.7寸、6.5寸和5.5寸的iphone进行截屏&#xff0c;假如支持ipad则还需要对ipad…

MySQL多表查询练习(53题)

MySQL多表查询练习 学生表、教师表、课程表、分数表 1、查询语文比数学成绩高的学生的信息以及课程分数 2、查询语文比数学成绩低的学生的信息以及课程分数 3、查询语文等于数学成绩的学生的信息以及课程分数 4、查询平均成绩大于等于60分的同学的学生编号&#xff0c;学生姓名…

C语言:求阶乘倒数之和

&#xff08;1&#xff09;题目&#xff1a;求Sn11/21/3...1/n&#xff0c;保留4位小数。 &#xff08;2&#xff09;代码&#xff1a; #include "stdio.h" int main() {int n; // 整数ndouble sum 0.0; // 倒数之和printf("请输入一个整数&#xff1a;"…

探索AI与社交的交汇点:看Facebook如何引领智能化革命

在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;正成为各大科技公司变革的重要驱动力。作为全球领先的社交媒体平台&#xff0c;Facebook&#xff08;现Meta Platforms&#xff09;正处于这一智能化革命的前沿。通过不断创新和应用AI技术&#xff0c;Facebook…

气膜建筑的抗风与防火性能:保障仓储的安全—轻空间

气膜建筑以其独特的结构和材料优势&#xff0c;为仓储设施提供了可靠的安全保障。在应对自然灾害特别是强风和火灾时&#xff0c;气膜建筑展示了优异的抗风和防火性能。轻空间将详细探讨这些性能及其在实际应用中的表现。 气膜建筑的抗风能力源于其特殊的结构设计和高性能材料。…

【算法】普里姆算法解决修路问题

应用场景——修路问题 1.某地有 7 个村庄&#xff08;A&#xff0c;B&#xff0c;C&#xff0c;D&#xff0c;E&#xff0c;F&#xff0c;G&#xff09;&#xff0c;现在需要修路把 7 个村庄连通 2.各个村庄的距离用边线表示&#xff08;权&#xff09;&#xff0c;比如 A - …

学习日志8.7--防火墙安全策略

安全区域之间的数据流动方向&#xff0c;是根据安全级别的优先级来定义的&#xff0c;如果是从优先级高的地方到优先级低的地方&#xff0c;比如说从Local&#xff08;100&#xff09;发送到Trust&#xff08;85&#xff09;是outbound&#xff0c;如果是从优先级低的地方到优先…

回归预测|基于雪消融优化极端梯度提升树的数据回归预测Matlab程序SAO-XGBoost多特征输入单输出 含基础模型

回归预测|基于雪消融优化极端梯度提升树的数据回归预测Matlab程序SAO-XGBoost多特征输入单输出 含基础模型 文章目录 前言回归预测|基于雪消融优化极端梯度提升树的数据回归预测Matlab程序SAO-XGBoost多特征输入单输出 含基础模型 一、SAO-XGBoost模型二、实验结果三、核心代码…

SolidEdge二次开发(C#)-遍历装配体

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1、前言2、在SolidEdge中创建一个装配体模型3、遍历的代码 1、前言 SolidEdge二次开发过程中&#xff0c;针对装配体的遍历是采用递归方法来完成的&#xff0c;能获…

Android adb启动任意app的几种方式

使用adb启动应用程序主要有两种方式&#xff1a;一种是已知应用程序的包名和主Activity&#xff0c;另一种是不知道应用程序的包名和主Activity。 已知应用程序的包名和主Activity 在这种情况下&#xff0c;我们可以通过输入特定的adb命令来启动应用程序。具体步骤如下&#x…

STM32 GPIO 模块

B站视频地址&#xff1a;芯片内部GPIO模块细节 操作 LED 灯&#xff0c;4个步骤 使能 GPIO 模块&#xff08;GPIO 模块&#xff0c;默认不工作&#xff09;选择 PIN2 的功能&#xff1a;连接到 GPIO 模块配置 GPIO 模块&#xff0c;让引脚&#xff0c;作为输出引脚配置 GPIO …

kubernets学习笔记——kubernets的相关概念

目录 理解 Kubernets 技术1、什么是 Kubernets2、为什么使用 Kubernets Kubernets 重要概念1、先看 Kubernets 架构图2、集群&#xff08;cluster&#xff09;3、主节点&#xff08;Master&#xff09;  3.1、API 服务器&#xff08;Kubernets API Server&#xff09;  3.2…

VC 与 VS(visual studio) 的对应版本

VC 与 VS 对应版本的关系&#xff1a; VC9&#xff1a;对应的是 Visual Studio 2008 版本。在这个版本中&#xff0c;开发环境提供了一系列的新特性和改进&#xff0c;为开发者提供了更高效的编程体验。例如&#xff0c;增强了对 C 标准的支持&#xff0c;优化了调试工具等。 …