随着无纸化、智慧教室等场景的普及,好多企业或者开发者开始寻求更高效稳定低延迟的RTMP同屏方案,本文以大牛直播SDK(Github)的同屏demo(对应工程:SmartServicePublisherV2)为例,介绍下如何采集编码推送RTMP数据到流媒体服务器。
系统要求:Android 5.0及以上系统。
废话不多说,上代码:
获取screen windows宽高,如需缩放,按照一定的比例缩放即可:
private void createScreenEnvironment() {
sreenWindowWidth = mWindowManager.getDefaultDisplay().getWidth();
screenWindowHeight = mWindowManager.getDefaultDisplay().getHeight();
Log.i(TAG, "screenWindowWidth: " + sreenWindowWidth + ",screenWindowHeight: "
+ screenWindowHeight);
if (sreenWindowWidth > 800)
{
if (screenResolution == SCREEN_RESOLUTION_STANDARD)
{
scale_rate = SCALE_RATE_HALF;
sreenWindowWidth = align(sreenWindowWidth / 2, 16);
screenWindowHeight = align(screenWindowHeight / 2, 16);
}
else if(screenResolution == SCREEN_RESOLUTION_LOW)
{
scale_rate = SCALE_RATE_TWO_FIFTHS;
sreenWindowWidth = align(sreenWindowWidth * 2 / 5, 16);
}
}
Log.i(TAG, "After adjust mWindowWidth: " + sreenWindowWidth + ", mWindowHeight: " + screenWindowHeight);
int pf = mWindowManager.getDefaultDisplay().getPixelFormat();
Log.i(TAG, "display format:" + pf);
DisplayMetrics displayMetrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(displayMetrics);
mScreenDensity = displayMetrics.densityDpi;
mImageReader = ImageReader.newInstance(sreenWindowWidth,
screenWindowHeight, 0x1, 6);
mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
}
获取到image数据后,传递到processScreenImage()处理:
private void setupMediaProjection() {
mMediaProjection = mMediaProjectionManager.getMediaProjection(
MainActivity.mResultCode, MainActivity.mResultData);
}
private void setupVirtualDisplay() {
mVirtualDisplay = mMediaProjection.createVirtualDisplay(
"ScreenCapture", sreenWindowWidth, screenWindowHeight,
mScreenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
mImageReader.setOnImageAvailableListener(
new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = mImageReader.acquireLatestImage();
if (image != null) {
processScreenImage(image);
//image.close();
}
}
}, null);
}
数据放到image list里面:
private void pushImage(Image image)
{
if ( null ==image )
return;
final int image_list_max_count = 1;
LinkedList close_images = null;
synchronized (image_list_lock)
{
if (image_list.size() > image_list_max_count )
{
close_images = new LinkedList();
while ( image_list.size() > image_list_max_count)
{
close_images.add(image_list.poll());
}
}
image_list.add(image);
}
if ( close_images != null )
{
while( !close_images.isEmpty() ) {
Image i = close_images.poll();
if ( i != null )
{
i.close();
//Log.i("PushImage", "drop image");
}
}
}
}
调用大牛直播SDK的RTMP初始化和参数设置接口:
libPublisher = new SmartPublisherJniV2();
private void InitAndSetConfig() {
//开始要不要采集音频或视频,请自行设置
publisherHandle = libPublisher.SmartPublisherOpen(this.getApplicationContext(),
audio_opt, video_opt, sreenWindowWidth,
screenWindowHeight);
if ( publisherHandle == 0 )
{
return;
}
Log.i(TAG, "publisherHandle=" + publisherHandle);
libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandeV2());
if(videoEncodeType == 1)
{
int h264HWKbps = setHardwareEncoderKbps(true, sreenWindowWidth,
screenWindowHeight);
Log.i(TAG, "h264HWKbps: " + h264HWKbps);
int isSupportH264HWEncoder = libPublisher
.SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps);
if (isSupportH264HWEncoder == 0) {
Log.i(TAG, "Great, it supports h.264 hardware encoder!");
}
}
else if (videoEncodeType == 2)
{
int hevcHWKbps = setHardwareEncoderKbps(false, sreenWindowWidth,
screenWindowHeight);
Log.i(TAG, "hevcHWKbps: " + hevcHWKbps);
int isSupportHevcHWEncoder = libPublisher
.SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps);
if (isSupportHevcHWEncoder == 0) {
Log.i(TAG, "Great, it supports hevc hardware encoder!");
}
}
if(is_sw_vbr_mode)
{
int is_enable_vbr = 1;
int video_quality = CalVideoQuality(sreenWindowWidth,
screenWindowHeight, true);
int vbr_max_bitrate = CalVbrMaxKBitRate(sreenWindowWidth,
screenWindowHeight);
libPublisher.SmartPublisherSetSwVBRMode(publisherHandle, is_enable_vbr, video_quality, vbr_max_bitrate);
}
//音频相关可以参考SmartPublisher工程
/*
if (!is_speex)
{
// set AAC encoder
libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 1);
}
else
{
// set Speex encoder
libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 2);
libPublisher.SmartPublisherSetSpeexEncoderQuality(publisherHandle, 8);
}
libPublisher.SmartPublisherSetNoiseSuppression(publisherHandle, is_noise_suppression ? 1
: 0);
libPublisher.SmartPublisherSetAGC(publisherHandle, is_agc ? 1 : 0);
*/
// libPublisher.SmartPublisherSetClippingMode(publisherHandle, 0);
//libPublisher.SmartPublisherSetSWVideoEncoderProfile(publisherHandle, sw_video_encoder_profile);
//libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, sw_video_encoder_speed);
// libPublisher.SetRtmpPublishingType(publisherHandle, 0);
libPublisher.SmartPublisherSetFPS(publisherHandle, 18); //帧率可调
libPublisher.SmartPublisherSetGopInterval(publisherHandle, 18*3);
//libPublisher.SmartPublisherSetSWVideoBitRate(publisherHandle, 1200, 2400); //针对软编码有效,一般最大码率是平均码率的二倍
libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, 3);
//libPublisher.SmartPublisherSaveImageFlag(publisherHandle, 1);
}
初始化、参数设置后,设置RTMP推送的URL,并调用SartPublisher()接口,开始推送:
//如果同时推送和录像,设置一次就可以
InitAndSetConfig();
if ( publisherHandle == 0 )
{
stopScreenCapture();
return;
}
if(push_type == PUSH_TYPE_RTMP)
{
String publishURL = intent.getStringExtra("PUBLISHURL");
Log.i(TAG, "publishURL: " + publishURL);
if (libPublisher.SmartPublisherSetURL(publisherHandle, publishURL) != 0) {
stopScreenCapture();
Log.e(TAG, "Failed to set publish stream URL..");
if (publisherHandle != 0) {
if (libPublisher != null) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}
return;
}
}
//启动传递数据线程
post_data_thread = new Thread(new DataRunnable());
Log.i(TAG, "new post_data_thread..");
is_post_data_thread_alive = true;
post_data_thread.start();
int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);
if (startRet != 0) {
isPushingRtmp = false;
Log.e(TAG, "Failed to start push rtmp stream..");
return;
}
//如果同时推送和录像,Audio启动一次就可以了
CheckInitAudioRecorder();
开始推送后,传递数据到底层SDK:
public class DataRunnable implements Runnable{
private final static String TAG = "DataRunnable==> ";
@Override
public void run() {
// TODO Auto-generated method stub
Log.i(TAG, "post data thread is running..");
ByteBuffer last_buffer = null;
Image last_image = null;
long last_post_time = System.currentTimeMillis();
while (is_post_data_thread_alive)
{
boolean is_skip = false;
/*
synchronized (data_list_lock)
{
if ( data_list.isEmpty())
{
if((System.currentTimeMillis() - last_post_time) > frame_added_interval_setting)
{
if(last_buffer != null)
{
Log.i(TAG, "补帧中..");
}
else
{
is_skip = true;
}
}
else
{
is_skip = true;
}
}
else
{
last_buffer = data_list.get(0);
data_list.remove(0);
}
}
*/
Image new_image = popImage();
if ( new_image == null )
{
if((System.currentTimeMillis() - last_post_time) > frame_added_interval_setting)
{
if(last_image != null)
{
Log.i(TAG, "补帧中..");
}
else
{
is_skip = true;
}
}
else
{
is_skip = true;
}
}
else
{
if ( last_image != null )
{
last_image.close();
}
last_image = new_image;
}
if( is_skip )
{
// Log.i("OnScreenImage", "is_skip");
try {
Thread.sleep(5); //休眠5ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else
{
//if( last_buffer != null && publisherHandle != 0 && (isPushing || isRecording || isRTSPPublisherRunning) )
if( last_image != null && publisherHandle != 0 && (isPushingRtmp || isRecording || isRTSPPublisherRunning) )
{
long post_begin_time = System.currentTimeMillis();
final Image.Plane[] planes = last_image.getPlanes();
if ( planes != null && planes.length > 0 )
{
libPublisher.SmartPublisherOnCaptureVideoRGBAData(publisherHandle, planes[0].getBuffer(), planes[0].getRowStride(),
last_image.getWidth(), last_image.getHeight());
}
last_post_time = System.currentTimeMillis();
long post_cost_time = last_post_time - post_begin_time;
if ( post_cost_time >=0 && post_cost_time < 10 )
{
try {
Thread.sleep(10-post_cost_time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
libPublisher.SmartPublisherOnCaptureVideoRGBAData(publisherHandle, last_buffer, row_stride_,
width_, height_);
*/
/*
//实际裁剪比例,可酌情自行调整
int left = 100;
int cliped_left = 0;
int top = 0;
int cliped_top = 0;
int cliped_width = width_;
int cliped_height = height_;
if(scale_rate == SCALE_RATE_HALF)
{
cliped_left = left / 2;
cliped_top = top / 2;
//宽度裁剪后,展示3/4比例
cliped_width = (width_ *3)/4;
//高度不做裁剪
cliped_height = height_;
}
else if(scale_rate == SCALE_RATE_TWO_FIFTHS)
{
cliped_left = left * 2 / 5;
cliped_top = top * 2 / 5;
//宽度裁剪后,展示3/4比例
cliped_width = (width_ *3)/4;
//高度不做裁剪
cliped_height = height_;
}
if(cliped_width % 2 != 0)
{
cliped_width = cliped_width + 1;
}
if(cliped_height % 2 != 0)
{
cliped_height = cliped_height + 1;
}
if ( (cliped_left + cliped_width) > width_)
{
Log.e(TAG, " invalid cliped region settings, cliped_left: " + cliped_left + " cliped_width:" + cliped_width + " width:" + width_);
return;
}
if ( (cliped_top + cliped_height) > height_)
{
Log.e(TAG, "invalid cliped region settings, cliped_top: " + cliped_top + " cliped_height:" + cliped_height + " height:" + height_);
return;
}
//Log.i(TAG, " clipLeft: " + cliped_left + " clipTop: " + cliped_top + " clipWidth: " + cliped_width + " clipHeight: " + cliped_height);
libPublisher.SmartPublisherOnCaptureVideoClipedRGBAData(publisherHandle, last_buffer, row_stride_,
width_, height_, cliped_left, cliped_top, cliped_width, cliped_height );
*/
// Log.i(TAG, "post data: " + last_post_time + " cost:" + post_cost_time);
}
else
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
if ( last_image != null)
{
last_image.close();
last_image = null;
}
}
}
关闭采集推送:
public void onDestroy() {
// TODO Auto-generated method stub
Log.i(TAG, "Service stopped..");
stopScreenCapture();
clearAllImages();
if( is_post_data_thread_alive && post_data_thread != null )
{
Log.i(TAG, "onDestroy close post_data_thread++");
is_post_data_thread_alive = false;
try {
post_data_thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
post_data_thread = null;
Log.i(TAG, "onDestroy post_data_thread closed--");
}
if (isPushingRtmp || isRecording || isRTSPPublisherRunning)
{
if (audioRecord_ != null) {
Log.i(TAG, "surfaceDestroyed, call StopRecording..");
audioRecord_.Stop();
if (audioRecordCallback_ != null) {
audioRecord_.RemoveCallback(audioRecordCallback_);
audioRecordCallback_ = null;
}
audioRecord_ = null;
}
stopPush();
isPushingRtmp = false;
stopRecorder();
isRecording = false;
stopRtspPublisher();
isRTSPPublisherRunning = false;
stopRtspService();
isRTSPServiceRunning = false;
if (publisherHandle != 0) {
if (libPublisher != null) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}
}
libPublisher.UnInitRtspServer();
super.onDestroy();
}
以上就是Android平台数据采集、编码并推送的大概流程,感兴趣的开发者可参考下。