Android 使用librtmp推流【推流接口调用模块】
来源:互联网 发布:js精粹 编辑:程序博客网 时间:2024/06/02 02:24
推流调用流程:init()-->initAudioGatherer(),initEncoders()-->startGather(),startEncoder(),starPublish()
推流接口调用代码如下:
package com.blueberry.media;import android.app.Activity;import android.media.MediaCodec;import android.util.Log;import android.view.SurfaceHolder;import java.io.IOException;import java.nio.ByteBuffer;import java.util.Arrays;import java.util.concurrent.LinkedBlockingQueue;/** * Created by blueberry on 3/7/2017. * 发送数据。 */public class MediaPublisher { private static final String TAG = "MediaPublisher"; private Config mConfig; public static final int NAL_SLICE = 1; public static final int NAL_SLICE_DPA = 2; public static final int NAL_SLICE_DPB = 3; public static final int NAL_SLICE_DPC = 4; public static final int NAL_SLICE_IDR = 5; public static final int NAL_SEI = 6; public static final int NAL_SPS = 7; public static final int NAL_PPS = 8; public static final int NAL_AUD = 9; public static final int NAL_FILLER = 12; private LinkedBlockingQueue<Runnable> mRunnables = new LinkedBlockingQueue<>(); private Thread workThread; private VideoGatherer mVideoGatherer; private AudioGatherer mAudioGatherer; private MediaEncoder mMediaEncoder; private RtmpPublisher mRtmpPublisher; public boolean isPublish; private AudioGatherer.Params audioParams; private VideoGatherer.Params videoParams; private boolean loop; public static MediaPublisher newInstance(Config config) { return new MediaPublisher(config); } private MediaPublisher(Config config) { this.mConfig = config; } /** * 初始化视频采集器,音频采集器,视频编码器,音频编码器 */ public void init() { mVideoGatherer = VideoGatherer .newInstance(mConfig); mAudioGatherer = AudioGatherer.newInstance(mConfig); mMediaEncoder = MediaEncoder.newInstance(mConfig); mRtmpPublisher = RtmpPublisher.newInstance(); setListener(); workThread = new Thread("publish-thread") { @Override public void run() { while (loop && !Thread.interrupted()) { try { Runnable runnable = mRunnables.take(); runnable.run(); } catch (InterruptedException e) { e.printStackTrace(); } } } }; loop = true; workThread.start(); } /** * 初始化音频采集器 */ public void initAudioGatherer() { audioParams = mAudioGatherer.initAudioDevice(); } /** * 初始化摄像头 * * @param act * @param holder */ public void initVideoGatherer(Activity act, SurfaceHolder holder) { videoParams = mVideoGatherer.initCamera(act, holder); } /** * 开始采集 */ public void startGather() { mAudioGatherer.start(); } /** * 初始化编码器 */ public void initEncoders() { try { mMediaEncoder.initAudioEncoder(audioParams.sampleRate, audioParams.channelCount); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "初始化音频编码器失败"); } try { int colorFormat = mMediaEncoder.initVideoEncoder(videoParams.previewWidth, videoParams.previewHeight, mConfig.fps); mVideoGatherer.setColorFormat(colorFormat); } catch (IOException e) { e.printStackTrace(); } } /** * 开始编码 */ public void startEncoder() { mMediaEncoder.start(); } /** * 发布 */ public void starPublish() { if (isPublish) { return; } Runnable runnable = new Runnable() { @Override public void run() { //初始化 int ret = mRtmpPublisher.init(mConfig.publishUrl, videoParams.previewWidth, videoParams.previewHeight, mConfig.timeOut); if (ret < 0) { Log.e(TAG, "连接失败"); return; } isPublish = true; } }; mRunnables.add(runnable); } /** * 停止发布 */ public void stopPublish() { Runnable runnable = new Runnable() { @Override public void run() { mRtmpPublisher.stop(); loop = false; workThread.interrupt(); } }; mRunnables.add(runnable); } /** * 停止编码 */ public void stopEncoder() { mMediaEncoder.stop(); } /** * 停止采集 */ public void stopGather() { mAudioGatherer.stop(); } /** * 释放 */ public void release() { Log.i(TAG, "release: "); mMediaEncoder.release(); mVideoGatherer.release(); mAudioGatherer.release(); loop = false; if (workThread != null) { workThread.interrupt(); } } private void setListener() { mVideoGatherer.setCallback(new VideoGatherer.Callback() { @Override public void onReceive(byte[] data, int colorFormat) { if (isPublish) { mMediaEncoder.putVideoData(data); } } }); mAudioGatherer.setCallback(new AudioGatherer.Callback() { @Override public void audioData(byte[] data) { if (isPublish) { mMediaEncoder.putAudioData(data); } } }); mMediaEncoder.setCallback(new MediaEncoder.Callback() { @Override public void outputVideoData(ByteBuffer bb, MediaCodec.BufferInfo info) { onEncodedAvcFrame(bb, info); } @Override public void outputAudioData(ByteBuffer bb, MediaCodec.BufferInfo info) { onEncodeAacFrame(bb, info); } }); } private void onEncodedAvcFrame(ByteBuffer bb, final MediaCodec.BufferInfo vBufferInfo) { int offset = 4; //判断帧的类型 if (bb.get(2) == 0x01) { offset = 3; } int type = bb.get(offset) & 0x1f; Log.d(TAG, "type=" + type); if (type == NAL_SPS) { //[0, 0, 0, 1, 103, 66, -64, 13, -38, 5, -126, 90, 1, -31, 16, -115, 64, 0, 0, 0, 1, 104, -50, 6, -30] //打印发现这里将 SPS帧和 PPS帧合在了一起发送 // SPS为 [4,len-8] // PPS为后4个字节 final byte[] pps = new byte[4]; final byte[] sps = new byte[vBufferInfo.size - 12]; bb.getInt();// 抛弃 0,0,0,1 bb.get(sps, 0, sps.length); bb.getInt(); bb.get(pps, 0, pps.length); Log.d(TAG, "解析得到 sps:" + Arrays.toString(sps) + ",PPS=" + Arrays.toString(pps)); Runnable runnable = new Runnable() { @Override public void run() { mRtmpPublisher.sendSpsAndPps(sps, sps.length, pps, pps.length, vBufferInfo.presentationTimeUs / 1000); } }; try { mRunnables.put(runnable); } catch (InterruptedException e) { e.printStackTrace(); } } else if (type == NAL_SLICE || type == NAL_SLICE_IDR) { final byte[] bytes = new byte[vBufferInfo.size]; bb.get(bytes); Runnable runnable = new Runnable() { @Override public void run() { mRtmpPublisher.sendVideoData(bytes, bytes.length, vBufferInfo.presentationTimeUs / 1000); } }; try { mRunnables.put(runnable); } catch (InterruptedException e) { e.printStackTrace(); } } } private void onEncodeAacFrame(ByteBuffer bb, final MediaCodec.BufferInfo aBufferInfo) { if (aBufferInfo.size == 2) { // 我打印发现,这里应该已经是吧关键帧计算好了,所以我们直接发送 final byte[] bytes = new byte[2]; bb.get(bytes); Runnable runnable = new Runnable() { @Override public void run() { mRtmpPublisher.sendAacSpec(bytes, 2); } }; try { mRunnables.put(runnable); } catch (InterruptedException e) { e.printStackTrace(); } } else { final byte[] bytes = new byte[aBufferInfo.size]; bb.get(bytes); Runnable runnable = new Runnable() { @Override public void run() { mRtmpPublisher.sendAacData(bytes, bytes.length, aBufferInfo.presentationTimeUs / 1000); } }; try { mRunnables.put(runnable); } catch (InterruptedException e) { e.printStackTrace(); } } }}
1.推流器初始化调用接口:init():
函数中首先创建 VideoGather,AudioGather,MediaEncoder,RtmpPublisher实例。
接着调用setListener()接口进行数据接口回调设定。
最后开启工作者线程,工作者线程的循环中从LinkedBlockingQueue<Runnable>类型的队列mRunnables中取数据。
该队列是线程安全的,当队列中没有数据时mRunnables.take()会阻塞。
2.setListen函数执行过程:
2.1给VideoGather设置回调处理类。当摄像头采集到数据后会回调onReceive,
该函数中调用mMediaEncoder.putVideoData将接收的数据放入到编码模块 mMediaEncoder.putVideoData
2.2给AudioGather设置回调处理类。当AudioRecord采集到数据后会回调 audioData,
该函数中调用mMediaEncoder.putAudioData将接收的数据放入到音频编码模块 mMediaEncoder.putAudioData
2.3给MediaEncoder设置回调处理类。当编码完视频数据时会回调 outputVideoData,该函数调用onEncodedAvcFrame将编码后的视频数据通过rtmp模块发送;
当编码完音频数据时会回调 outputAudioData,该函数调用onEncodedAacFrame将编码后的音频数据通过rtmp模块发送。
3.音视频数据发送
onEncodedAvcFrame,onEncodedAacFrame这两个函数都是将发送过程封装到Runnable类实例中,然后加入到mRunnables队列中,Runnable实例将在工作者线程中执行。
4.开始音视频采集
调用startGather(),分别在VideoGather和AudioGather模块中开启单独的线程的来进行视频采集和音频采集
5.开始音视频编码
调用startEncoder(),在MediaEncoder模块中开启视频编码,音频编码线程
6.发布和停止【starPublish(),stopPublish()】
开始和停止发布是通过将发送过程分装到Runnable类实例中,然后添加到Runnable队列中,放到工作者进程中执行。
- Android 使用librtmp推流【推流接口调用模块】
- Android 使用librtmp推流
- Android 使用librtmp推流【视频采集模块】
- Android 使用librtmp推流【音频采集模块】
- Android 使用librtmp推流【音视频编码模块】
- Android 使用librtmp推流【视频YUV格式转化】
- Android 使用librtmp推流【音视频传输】
- 树莓派使用python-librtmp实现rtmp推流h264
- h.264 aac librtmp推流应用
- android librtmp 推送h264流 aac流 基本过程总结四 推流x264
- android librtmp 推送h264流 aac流 基本过程总结五 推流aac
- 1小时学会:最简单的iOS直播推流(十)librtmp使用介绍
- Android 滤镜推流
- Android 使用Rtmp音视频推流
- Android 使用Rtmp音视频推流
- android个推消息推送,asp.net调用接口
- 推流
- Android直播推流学习
- 【自己的整理】socket.io官方demo|创建简单的聊天应用
- JDK动态代理实现原理
- 排序算法
- Waiting for .../output/faster_rcnn_end2end/voc_2007_trainval/VGGnet_fast_rcnn_iter_70000.ckpt
- SDUToj
- Android 使用librtmp推流【推流接口调用模块】
- 计算机网络笔记
- 文章标题
- Java 工程师岗位要求(支付)
- 【云星数据---Apache Flink实战系列(精品版)】:Apache Flink高级特性与高级应用012-Slot和Parallelism的深入分析007
- 微博第三方登录
- 一日一文(1)
- BaseWeb05-JSP
- 李飞飞高徒 Andrej Karpathy:计算机科学博士的生存指南