一.CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
add_subdirectory(librtmp)
add_library(
native-lib
SHARED
native-lib.cpp )
find_library(
log-lib
log )
target_link_libraries(
native-lib
${log-lib}
rtmp)
二.MainActivity
public class MainActivity extends AppCompatActivity {
private MediaProjectionManager mediaProjectionManager;
private MediaProjection mediaProjection;
ScreenLive screenLive;
String url = "rtmp://live-push.bilivideo.com/live-bvc/?streamname=live_436361523_69672384&key=654ce7137e852ab60fde72836c815a63&schedule=rtmp";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermission();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 100 && resultCode == Activity.RESULT_OK) {
mediaProjection = mediaProjectionManager.getMediaProjection
(resultCode, data);
screenLive = new ScreenLive();
screenLive.startLive(url, mediaProjection);
}
}
public void startLive(View view) {
this.mediaProjectionManager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, 100);
}
public boolean checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
}, 1);
}
return false;
}
public void stopLive(View view) {
}
}
三.RTMPPackage
public class RTMPPackage {
private byte[] buffer;
private long tms;
private int type;// 视频包 音频包
public static final int RTMP_PACKET_TYPE_AUDIO_DATA = 2;
public static final int RTMP_PACKET_TYPE_AUDIO_HEAD = 1;
public static final int RTMP_PACKET_TYPE_VIDEO = 0;
public RTMPPackage(byte[] buffer, long tms) {
this.buffer = buffer;
this.tms = tms;
}
public RTMPPackage( ) {
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public byte[] getBuffer() {
return buffer;
}
public void setBuffer(byte[] buffer) {
this.buffer = buffer;
}
public long getTms() {
return tms;
}
public void setTms(long tms) {
this.tms = tms;
}
}
四.VideoCodec
public class VideoCodec extends Thread {
private MediaProjection mediaProjection;
private VirtualDisplay virtualDisplay;
private MediaCodec mediaCodec;
private ScreenLive screenLive;
private boolean isLiving;
private long timeStamp;
private long startTime;
public VideoCodec(ScreenLive screenLive) {
this.screenLive = screenLive;
}
public void startLive(MediaProjection mediaProjection) {
this.mediaProjection = mediaProjection;
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 720, 1280);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, 400_000);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
try {
mediaCodec = MediaCodec.createEncoderByType("video/avc");
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface surface = mediaCodec.createInputSurface();
virtualDisplay = mediaProjection.createVirtualDisplay(
"screen-codec",
720, 1280, 1,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
surface, null, null);
} catch (IOException e) {
e.printStackTrace();
}
start();
}
@Override
public void run() {
isLiving = true;
mediaCodec.start();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (isLiving) {
if (System.currentTimeMillis() - timeStamp >= 2000) {
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
//dsp 芯片触发I帧
mediaCodec.setParameters(params);
timeStamp = System.currentTimeMillis();
}
int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000);
if (index >= 0) {
if (startTime == 0) {
startTime = bufferInfo.presentationTimeUs / 1000;
}
ByteBuffer buffer = mediaCodec.getOutputBuffer(index);
byte[] outData = new byte[bufferInfo.size];
buffer.get(outData);
RTMPPackage rtmpPackage = new RTMPPackage(outData, (bufferInfo.presentationTimeUs / 1000) - startTime);
screenLive.addPackage(rtmpPackage);
mediaCodec.releaseOutputBuffer(index, false);
}
}
isLiving = false;
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
virtualDisplay.release();
virtualDisplay = null;
mediaProjection.stop();
mediaProjection = null;
startTime = 0;
}
}
五.AudioCodec
public class AudioCodec extends Thread{
private static final String TAG = "AudioCodec";
private MediaCodec mediaCodec;
private int minBufferSize;
private boolean isRecoding;
private AudioRecord audioRecord;
private long startTime;
private ScreenLive screenLive;
// 录音状态
private volatile AudioRecorder.Status mStatus = AudioRecorder.Status.STATUS_NO_READY;
public AudioCodec(ScreenLive screenLive) {
this.screenLive = screenLive;
}
public void startLive() {
MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100, 2);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);//录音质量
format.setInteger(MediaFormat.KEY_BIT_RATE, 64_000);//一秒的码率 aac
try {
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
minBufferSize = AudioRecord.getMinBufferSize(44100,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT);
audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC, 44100,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBufferSize);
int state = audioRecord.getState();
Log.i(TAG, "createAudio state: " + state + ", initialized: " + (state == AudioRecord.STATE_INITIALIZED));
} catch (Exception e) {
}
LiveTaskManager.getInstance().execute(this);
}
@Override
public void run() {
isRecoding = true;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
// 没有开始编码音频 空 数据头
RTMPPackage rtmpPackage = new RTMPPackage();
byte[] audioDecoderSpecificInfo = {0x12, 0x08};
rtmpPackage.setBuffer(audioDecoderSpecificInfo);
rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_AUDIO_HEAD);
screenLive.addPackage(rtmpPackage);
if (mStatus == AudioRecorder.Status.STATUS_START) {
throw new IllegalStateException("正在录音...");
}
Log.d(TAG, "===startRecord===");
audioRecord.startRecording();//开始录音
byte[] buffer = new byte[minBufferSize];
while (isRecoding) {
int len = audioRecord.read(buffer, 0, buffer.length);
if (len <= 0) {
continue;
}
//立即得到有效输入缓冲区
int index = mediaCodec.dequeueInputBuffer(0);
if (index >= 0) {
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);
inputBuffer.clear();
inputBuffer.put(buffer, 0, len);
//填充数据后再加入队列
mediaCodec.queueInputBuffer(index, 0, len,
System.nanoTime() / 1000, 0);
}
index = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (index >= 0 && isRecoding) {
ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(index);
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
if (startTime == 0) {
startTime = bufferInfo.presentationTimeUs / 1000;
}
rtmpPackage = new RTMPPackage();
rtmpPackage.setBuffer(outData);
rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_AUDIO_DATA);
long tms = (bufferInfo.presentationTimeUs / 1000) - startTime;
rtmpPackage.setTms(tms);
screenLive.addPackage(rtmpPackage);
mediaCodec.releaseOutputBuffer(index, false);
index = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
}
audioRecord.stop();
audioRecord.release();
audioRecord = null;
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
startTime = 0;
isRecoding = false;
}
}
六.LiveTaskManager
public class LiveTaskManager {
private static volatile LiveTaskManager instance;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(5);
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
private LiveTaskManager() {
}
public static LiveTaskManager getInstance() {
if (instance == null) {
synchronized (LiveTaskManager.class) {
if (instance == null) {
instance = new LiveTaskManager();
}
}
}
return instance;
}
public void execute(Runnable runnable) {
THREAD_POOL_EXECUTOR.execute(runnable);
}
}
七.ScreenLive
public class ScreenLive extends Thread {
private String url;
private MediaProjection mediaProjection;
private LinkedBlockingQueue<RTMPPackage> queue = new LinkedBlockingQueue<>();
private boolean isLiving;
static {
System.loadLibrary("native-lib");
}
public void startLive(String url, MediaProjection mediaProjection) {
this.url = url;
this.mediaProjection = mediaProjection;
LiveTaskManager.getInstance().execute(this);
}
@Override
public void run() {
if (!connect(url)) {
Log.i("liuyi", "run: ----------->推送失败");
return;
}
VideoCodec videoCodec = new VideoCodec(this);
videoCodec.startLive(mediaProjection);
AudioCodec audioCodec = new AudioCodec(this);
audioCodec.startLive();
isLiving = true;
while (isLiving) {
RTMPPackage rtmpPackage = null;
try {
rtmpPackage = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (rtmpPackage.getBuffer() != null && rtmpPackage.getBuffer().length != 0) {
Log.i("liuyi","java sendData");
sendData(rtmpPackage.getBuffer(), rtmpPackage.getBuffer().length, rtmpPackage.getTms(), rtmpPackage.getType());
}
}
}
public void addPackage(RTMPPackage rtmpPackage) {
if (!isLiving) {
return;
}
queue.add(rtmpPackage);
}
private native boolean connect(String url);
private native boolean sendData(byte[] data, int len, long tms, int type);
}
}
八.native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_INFO,"liuyi",__VA_ARGS__)
extern "C"{
#include "librtmp/rtmp.h"
}
typedef struct {
RTMP *rtmp;
int16_t sps_len;
int8_t *sps;
int16_t pps_len;
int8_t *pps;
}Live;
Live *live = NULL;
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_luisliuyi_demo_camera1_ScreenLive_connect(JNIEnv *env, jobject thiz, jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
int ret;
do {
live = (Live*)malloc(sizeof(Live));
memset(live, 0, sizeof(Live));
live->rtmp = RTMP_Alloc();
RTMP_Init(live->rtmp);
live->rtmp->Link.timeout = 10;
LOGE("connect %s", url);
if (!(ret = RTMP_SetupURL(live->rtmp, (char*)url))) break;
RTMP_EnableWrite(live->rtmp);
LOGE("RTMP_Connect");
if (!(ret = RTMP_Connect(live->rtmp, 0))) break;
LOGE("RTMP_ConnectStream ");
if (!(ret = RTMP_ConnectStream(live->rtmp, 0))) break;
LOGE("connect success");
} while (0);
if (!ret && live) {
free(live);
live = nullptr;
}
env->ReleaseStringUTFChars(url_, url);
return ret;
}
// 传递第一帧 00 00 00 01 67 64 00 28ACB402201E3CBCA41408081B4284D4 00000001 68 EE 06 F2 C0
void prepareVideo(int8_t *data, int len, Live *live) {
for (int i = 0; i < len; i++) {
if (i + 4 < len) {
if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x00 && data[i + 3] == 0x01) {
if (data[i + 4] == 0x68) {
//sps解析
live->sps_len = i - 4;
live->sps = static_cast<int8_t *>(malloc(live->sps_len));
memcpy(live->sps, data + 4, live->sps_len);
//pps解析
live->pps_len = len - (4 + live->sps_len) - 4;
live->pps = static_cast<int8_t *>(malloc(live->pps_len));
memcpy(live->pps, data + 4 + live->sps_len + 4, live->pps_len);
break;
}
}
}
}
}
//sps pps 的 packaet
RTMPPacket *createVideoPackage(Live *live) {
int body_size = 16 + live->sps_len + live->pps_len; //为什么是16????参考rtmp视频包结构
RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
RTMPPacket_Alloc(packet, body_size);
int i = 0;
packet->m_body[i++] = 0x17;
packet->m_body[i++] = 0x00;
packet->m_body[i++] = 0x00;
packet->m_body[i++] = 0x00;
packet->m_body[i++] = 0x00;
packet->m_body[i++] = 0x01;
packet->m_body[i++] = live->sps[1]; //profile 如baseline、main、 high
packet->m_body[i++] = live->sps[2]; //profile_compatibility 兼容性
packet->m_body[i++] = live->sps[3]; //profile level
packet->m_body[i++] = 0xFF;
packet->m_body[i++] = 0xE1;
//sps length
packet->m_body[i++] = (live->sps_len >> 8) & 0xFF;//高八位
packet->m_body[i++] = live->sps_len & 0xff;//低八位
//拷贝sps的内容
memcpy(&packet->m_body[i], live->sps, live->sps_len);
i +=live->sps_len;
packet->m_body[i++] = 0x01;
//pps length
packet->m_body[i++] = (live->pps_len >> 8) & 0xff; //高八位
packet->m_body[i++] = live->pps_len & 0xff;//低八位
// 拷贝pps内容
memcpy(&packet->m_body[i], live->pps, live->pps_len);
//视频类型
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet->m_nBodySize = body_size;
packet->m_nChannel = 0x04;
packet->m_nTimeStamp = 0;
packet->m_hasAbsTimestamp = 0;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nInfoField2 = live->rtmp->m_stream_id;
return packet;
}
RTMPPacket *createVideoPackage(int8_t *buf, int len, const long tms, Live *live) {
buf += 4;
RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
int body_size = len + 9;
//初始化RTMP内部的body数组
RTMPPacket_Alloc(packet, body_size);
if (buf[0] == 0x65) {//
packet->m_body[0] = 0x17;
LOGE("发送关键帧 data");
} else{
packet->m_body[0] = 0x27;
LOGE("发送非关键帧 data");
}
packet->m_body[1] = 0x01;
packet->m_body[2] = 0x00;
packet->m_body[3] = 0x00;
packet->m_body[4] = 0x00;
//长度
packet->m_body[5] = (len >> 24) & 0xff;
packet->m_body[6] = (len >> 16) & 0xff;
packet->m_body[7] = (len >> 8) & 0xff;
packet->m_body[8] = (len) & 0xff;
//数据
memcpy(&packet->m_body[9], buf, len);//为什么是9????参考rtmp视频包结构
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet->m_nBodySize = body_size;
packet->m_nChannel = 0x04;
packet->m_nTimeStamp = tms;
packet->m_hasAbsTimestamp = 0;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nInfoField2 = live->rtmp->m_stream_id;
return packet;
}
int sendPacket(RTMPPacket *packet) {
int r = RTMP_SendPacket(live->rtmp, packet, 1);
if(r){
LOGE("发送rtmp包成功");
}
RTMPPacket_Free(packet);
free(packet);
return r;
}
// 传递第一帧 00 00 00 01 67 64 00 28ACB402201E3CBCA41408081B4284D4 0000000168 EE 06 F2 C0
int sendVideo(int8_t *buf, int len, long tms) {
int ret = 0;
if (buf[4] == 0x67) {
// 缓存sps 和pps 到全局遍历 不需要推流
if (live && (!live->pps || !live->sps)) {
prepareVideo(buf, len, live);
}
return ret;
}
if (buf[4] == 0x65) {//关键帧
RTMPPacket *packet = createVideoPackage(live);
sendPacket(packet);
}
RTMPPacket *packet2 = createVideoPackage(buf, len, tms, live);
ret = sendPacket(packet2);
return ret;
}
RTMPPacket *createAudioPacket(int8_t *buf, const int len, const int type, const long tms,
Live *live) {
int body_size = len + 2;
RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
RTMPPacket_Alloc(packet, body_size);
// 音频头
packet->m_body[0] = 0xAF;
if (type == 1) {
packet->m_body[1] = 0x00;//头
} else{
packet->m_body[1] = 0x01;
}
memcpy(&packet->m_body[2], buf, len);
packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
packet->m_nChannel = 0x05;
packet->m_nBodySize = body_size;
packet->m_nTimeStamp = tms;
packet->m_hasAbsTimestamp = 0;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nInfoField2 = live->rtmp->m_stream_id;
return packet;
}
int sendAudio(int8_t *buf, int len, int type, int tms) {
RTMPPacket *packet = createAudioPacket(buf, len, type, tms, live);
int ret=sendPacket(packet);
return ret;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_luisliuyi_demo_camera1_ScreenLive_sendData(JNIEnv *env, jobject thiz, jbyteArray data_,
jint len, jlong tms, jint type) {
int ret;
jbyte *data = env->GetByteArrayElements(data_, NULL);
switch (type) {
case 0: //video
ret = sendVideo(data, len, tms);
break;
default: //audio
ret = sendAudio(data, len, type, tms);
break;
}
env->ReleaseByteArrayElements(data_, data, 0);
return ret;
}
九.代码地址
https://gitee.com/luisliuyi/android-rtmp-mediaprojection.git