Android Media 09 --- RTMP推流(librtmp+錄屏)

一.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;

   public RTMPPackage(byte[] buffer, long tms) {
       this.buffer = buffer;
       this.tms = tms;
   }

   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 芯片觸發(fā)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;
   }


}

五.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;
       start();
   }

   @Override
   public void run() {
       if (!connect(url)) {
           Log.i("liuyi", "run: ----------->推送失敗");
           return;
       }

       VideoCodec videoCodec = new VideoCodec(this);
       videoCodec.startLive(mediaProjection);

       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());
           }
       }
   }

   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);

}

六.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視頻包結(jié)構(gòu)
   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的內(nèi)容
   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內(nèi)容
   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內(nèi)部的body數(shù)組
   RTMPPacket_Alloc(packet, body_size);

   if (buf[0] == 0x65) {//
       packet->m_body[0] = 0x17;
       LOGE("發(fā)送關(guān)鍵幀 data");
   } else{
       packet->m_body[0] = 0x27;
       LOGE("發(fā)送非關(guān)鍵幀 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;

   //數(shù)據(jù)
   memcpy(&packet->m_body[9], buf, len);//為什么是9韧献?竣贪??玫荣?參考rtmp視頻包結(jié)構(gòu)

   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("發(fā)送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) {//關(guān)鍵幀
       RTMPPacket *packet = createVideoPackage(live);
       sendPacket(packet);
   }

   RTMPPacket *packet2 = createVideoPackage(buf, len, tms, live);
   ret = sendPacket(packet2);
   return ret;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_luisliuyi_demo_camera1_ScreenLive_sendData(JNIEnv *env, jobject thiz, jbyteArray data_,
                                                   jint len, jlong tms) {
   int ret;
   jbyte *data = env->GetByteArrayElements(data_, NULL);
   ret = sendVideo(data, len, tms);
   env->ReleaseByteArrayElements(data_, data, 0);
   return ret;
}

七.代碼地址

https://gitee.com/luisliuyi/android-rtmp-mediaprojection.git
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甚淡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捅厂,更是在濱河造成了極大的恐慌贯卦,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焙贷,死亡現(xiàn)場離奇詭異撵割,居然都是意外死亡,警方通過查閱死者的電腦和手機辙芍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門啡彬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羹与,“玉大人,你說我怎么就攤上這事庶灿∽莞椋” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵往踢,是天一觀的道長腾誉。 經(jīng)常有香客問我,道長菲语,這世上最難降的妖魔是什么妄辩? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮山上,結(jié)果婚禮上眼耀,老公的妹妹穿的比我還像新娘。我一直安慰自己佩憾,他們只是感情好哮伟,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妄帘,像睡著了一般楞黄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抡驼,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天鬼廓,我揣著相機與錄音,去河邊找鬼致盟。 笑死碎税,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的馏锡。 我是一名探鬼主播雷蹂,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杯道!你這毒婦竟也來了匪煌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤党巾,失蹤者是張志新(化名)和其女友劉穎萎庭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體齿拂,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡驳规,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了创肥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叹侄,靈堂內(nèi)的尸體忽然破棺而出巩搏,到底是詐尸還是另有隱情,我是刑警寧澤趾代,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布贯底,位于F島的核電站,受9級特大地震影響撒强,放射性物質(zhì)發(fā)生泄漏禽捆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一飘哨、第九天 我趴在偏房一處隱蔽的房頂上張望胚想。 院中可真熱鬧,春花似錦芽隆、人聲如沸浊服。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牙躺。三九已至,卻和暖如春腕扶,著一層夾襖步出監(jiān)牢的瞬間孽拷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工半抱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脓恕,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓代虾,卻偏偏與公主長得像进肯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子棉磨,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容