美文网首页音视频
Android Media 10 --- RTMP推流(libr

Android Media 10 --- RTMP推流(libr

作者: 沪漂意哥哥 | 来源:发表于2022-04-26 10:19 被阅读0次

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

    相关文章

      网友评论

        本文标题:Android Media 10 --- RTMP推流(libr

        本文链接:https://www.haomeiwen.com/subject/unxfyrtx.html