思路

  1. 要实现直播我们必须有3个东西 推流端、流媒体服务器、播放端。
  2. 流媒体服务器我们可以暂时不考虑,可以直接使用开源的服务器red5或nginx等。
  3. 推流端设计:
    • 如何进行音频、视频采集
    • 如何进行音频、视频编码
    • 如何音视频一起实时发送
  4. 播放端设计:
    • 如何接收音频、视频包
    • 如何进行音频、视频解码
    • 如何播放音频、视频

本文采用的协议

  1. 我们音频采用AAC编码、视频采用H264编码。
  2. 推送采用RTMP协议。

推送端实现

推送端的实现我在曾经的一遍文章中已经讲过,文章地址 http://blog.csdn.net/a992036795/article/details/54583571 ,这篇文章中代码为了便于阅读,当时所有的核心代码都放在了MainActivity中,并且都运行在主线程所以造成和很多问题,我已经将代码整理并上传了github,末尾我将奉上项目地址。
由于代码量较多,我就只贴出部分代码,大家可以直接去我的github中查看完整代码。

音频采集

音频采集我使用一个单独的线程去采集,并将采集的数据放入到一个队列中。这个队列中的数据将会由音频编码器去消费。

采集音频代码:

     public void start() {workThread = new Thread() {@Overridepublic void run() {mAudioRecord.startRecording();while (loop && !Thread.interrupted()) {int size = mAudioRecord.read(buffer, 0, buffer.length);if (size < 0) {Log.i(TAG, "audio ignore ,no data to read");break;}if (loop) {byte[] audio = new byte[size];System.arraycopy(buffer, 0, audio, 0, size);if (mCallback != null) {mCallback.audioData(audio);}}}}};loop = true;workThread.start();}

回调传递数据:

     mAudioGatherer.setCallback(new AudioGatherer.Callback() {@Overridepublic void audioData(byte[] data) {if (isPublish) {mMediaEncoder.putAudioData(data);}}});

加入到队列:

    private LinkedBlockingQueue<byte[]> audioQueue;public void putAudioData(byte[] data) {try {audioQueue.put(data);} catch (InterruptedException e) {e.printStackTrace();}}

视频采集

视频采集和音频采集类似,也是启动一个线程将采集到的数据放入一个队列然后等待视频编码器去消费。只不过这里我们需要将采集到的视频处理成编码器支持的像素格式。我们设置摄像头参数的时候设置成:parameters.setPreviewFormat(ImageFormat.Nv21),这样我们在回调函数:public void onPreviewFrame(final byte[] data, final Camera camera) 拿到的数据像素格式就是Nv21格式。(Nv21是Yuv420格式的一种,Yuv420格式还分为Yuv420SP,Yuv420P等,大家可以搜索相关资料,网上也有许多转换的方法)
设置回调方法:

mCamera.setPreviewCallbackWithBuffer(getPreviewCallback());
mCamera.addCallbackBuffer(new byte[calculateFrameSize(ImageFormat.NV21)]);

回调出拿到NV21数据并放入一个队列,这个队列将有一个线程消费,负责将他的像素格式转换成编码器需要的像素格式:

public Camera.PreviewCallback getPreviewCallback() {return new Camera.PreviewCallback() {//            byte[] dstByte = new byte[calculateFrameSize(ImageFormat.NV21)];@Overridepublic void onPreviewFrame(final byte[] data, final Camera camera) {if (data != null) {if (isPublished) {try {mQueue.put(new PixelData(colorFormat, data));} catch (InterruptedException e) {e.printStackTrace();}}} else {camera.addCallbackBuffer(new byte[calculateFrameSize(ImageFormat.NV21)]);}}};
}

转换像素格式并放入一个队列,这个队列将由视频编码器消费:

private void initWorkThread() {workThread = new Thread() {private long preTime;//YUV420byte[] dstByte = new byte[calculateFrameSize(ImageFormat.NV21)];@Overridepublic void run() {while (loop && !Thread.interrupted()) {try {PixelData pixelData = mQueue.take();// 处理if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar) {Yuv420Util.Nv21ToYuv420SP(pixelData.data, dstByte, previewSize.width, previewSize.height);} else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) {Yuv420Util.Nv21ToI420(pixelData.data, dstByte, previewSize.width, previewSize.height);} else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible) {// Yuv420_888} else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar) {// Yuv420packedPlannar 和 yuv420sp很像// 区别在于 加入 width = 4的话 y1,y2,y3 ,y4公用 u1v1// 而 yuv420dp 则是 y1y2y5y6 共用 u1v1//这样处理的话颜色核能会有些失真。Yuv420Util.Nv21ToYuv420SP(pixelData.data, dstByte, previewSize.width, previewSize.height);} else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar) {} else {System.arraycopy(pixelData.data, 0, dstByte, 0, pixelData.data.length);}if (mCallback != null) {mCallback.onReceive(dstByte, colorFormat);}//处理完成之后调用 addCallbackBuffer()if (preTime != 0) {// 延时int shouldDelay = (int) (1000.0 / FPS);int realDelay = (int) (System.currentTimeMillis() - preTime);int delta = shouldDelay - realDelay;if (delta > 0) {sleep(delta);}}addCallbackBuffer(pixelData.data);preTime = System.currentTimeMillis();} catch (InterruptedException e) {e.printStackTrace();break;}}}};
}

音频编码

音视频编码我都采用Andorid自带的MediaCodec进行编码。

音频编码,读取音频采集线程放入队列的数据,进行编码

/*** 开始音频编码*/
public void startAudioEncode() {if (aEncoder == null) {throw new RuntimeException("请初始化音频编码器");}if (audioEncoderLoop) {throw new RuntimeException("必须先停止");}audioEncoderThread = new Thread() {@Overridepublic void run() {presentationTimeUs = System.currentTimeMillis() * 1000;aEncoder.start();while (audioEncoderLoop && !Thread.interrupted()) {try {byte[] data = audioQueue.take();encodeAudioData(data);} catch (InterruptedException e) {e.printStackTrace();break;}}}};audioEncoderLoop = true;audioEncoderThread.start();
}
/*** 音频编码** @param data*/
private void encodeAudioData(byte[] data) {ByteBuffer[] inputBuffers = aEncoder.getInputBuffers();ByteBuffer[] outputBuffers = aEncoder.getOutputBuffers();int inputBufferId = aEncoder.dequeueInputBuffer(-1);if (inputBufferId >= 0) {ByteBuffer bb = inputBuffers[inputBufferId];bb.clear();bb.put(data, 0, data.length);long pts = new Date().getTime() * 1000 - presentationTimeUs;aEncoder.queueInputBuffer(inputBufferId, 0, data.length, pts, 0);}int outputBufferId = aEncoder.dequeueOutputBuffer(aBufferInfo, 0);if (outputBufferId >= 0) {// outputBuffers[outputBufferId] is ready to be processed or rendered.ByteBuffer bb = outputBuffers[outputBufferId];if (mCallback != null) {mCallback.outputAudioData(bb, aBufferInfo);}aEncoder.releaseOutputBuffer(outputBufferId, false);}}

视频编码

视频编码和音频编码类似,也是使用MediaCodec进行编码

读取队列中的数据:

  /*** 开始视频编码*/public void startVideoEncode() {if (vEncoder == null) {throw new RuntimeException("请初始化视频编码器");}if (videoEncoderLoop) {throw new RuntimeException("必须先停止");}videoEncoderThread = new Thread() {@Overridepublic void run() {presentationTimeUs = System.currentTimeMillis() * 1000;vEncoder.start();while (videoEncoderLoop && !Thread.interrupted()) {try {byte[] data = videoQueue.take(); //待编码的数据encodeVideoData(data);} catch (InterruptedException e) {e.printStackTrace();break;}}}};videoEncoderLoop = true;videoEncoderThread.start();}

编码:

  /*** 视频编码** @param dstByte*/private void encodeVideoData(byte[] dstByte) {ByteBuffer[] inputBuffers = vEncoder.getInputBuffers();ByteBuffer[] outputBuffers = vEncoder.getOutputBuffers();int inputBufferId = vEncoder.dequeueInputBuffer(-1);if (inputBufferId >= 0) {// fill inputBuffers[inputBufferId] with valid dataByteBuffer bb = inputBuffers[inputBufferId];bb.clear();bb.put(dstByte, 0, dstByte.length);long pts = new Date().getTime() * 1000 - presentationTimeUs;vEncoder.queueInputBuffer(inputBufferId, 0, dstByte.length, pts, 0);}int outputBufferId = vEncoder.dequeueOutputBuffer(vBufferInfo, 0);if (outputBufferId >= 0) {// outputBuffers[outputBufferId] is ready to be processed or rendered.ByteBuffer bb = outputBuffers[outputBufferId];if (null != mCallback) {mCallback.outputVideoData(bb, vBufferInfo);}vEncoder.releaseOutputBuffer(outputBufferId, false);}}

最后我们将编码中的数据,和对应的发送操作封装成一个Runable对象,放入一个队列,等待推送线程推送。

音视频推送

推送使用开源的librtmp,这里将要使用到jni。详细的实现就在这里不说了,大家可以去看我的代码就好好了。jni层实现也只有一点。
Jni类,其中的发送就是用来发送h264数据,和aac数据。

public final class PublishJni {static {System.loadLibrary("publish");}static native long init(String url, int w, int h, int timeOut);static native int sendSpsAndPps(long cptr, byte[] sps, int spsLen, byte[] pps, int ppsLen, long timestamp);static native int sendVideoData(long cptr, byte[] data, int len, long timestamp);static native int sendAacSpec(long cptr, byte[] data, int len);static native int sendAacData(long cptr, byte[] data, int len, long timestamp);static native int stop(long cptr);}

服务器搭建

大家可以直接使用开源的服务器,我这里使用red5。下载安装、并配置环境变量。完毕之后输入red5命令就可以启动服务器了。

播放端

我使用开源的ijplayer框架,使用它示例中提供的ijplayer example 就可以直接播放了。

项目地址

https://github.com/blueberryCoder/LiveStream

Android直播解决方案相关推荐

  1. 手机视频直播解决方案

    手机视频直播解决方案 一.方案简介 本公司的手机视频直播.点播解决系统,是以2.75G.3G.WLAN网络的音视频压缩.传输技术为核心,将广电网.通信网.互联网的音视频应用无缝整合,为电视台.媒体.教 ...

  2. 监控摄像头RTSP低延时无插件直播解决方案

    监控摄像头RTSP低延时无插件直播解决方案 第一章 应用简介 当前,视频监控应用场景越来越多,传统的视频监控厂商提供的解决方案需要安装厂商自己的手机APP或PC客户端软件,非常不方便在互联网环境下与第 ...

  3. 人人皆可虚拟,直播还能这么玩?声网推出 MetaLive 元直播解决方案

    视频群聊.在线社交.电商带货.游戏竞技-越来越多的场景融入了直播这一功能.无可厚非,直播可以拉近人与人间的距离,让彼此间的交流更具象.但传统直播场景更为强调主播个人的表现,用户多以围观.刷弹幕.打赏等 ...

  4. 使用Java实现视频直播解决方案

    使用Java实现视频直播解决方案 1.概述 本博客使用JavaCV开发的rtsp流转rtmp流并进行推流,并使用nginx实现流媒体直播方案 1.1 网络摄像头协议(一般网络摄像头支持协议有GB/T2 ...

  5. android 自定义推流器,Android直播实现 Android端推流、播放

    最近想实现一个Android直播,但是对于这方面的资料都比较零碎,一开始是打算用ffmpeg来实现编码推流,在搜集资料期间,找到了几个强大的开源库,直接避免了jni的代码,集成后只用少量的java代码 ...

  6. android 推流地址可以多人用,Android直播实现(一)Android端推流、播放

    Android直播实现(一)Android端推流.播放 最近想实现一个Android直播,但是对于这方面的资料都比较零碎,一开始是打算用ffmpeg来实现编码推流,在搜集资料期间,找到了几个强大的开源 ...

  7. 无人机实时流怎么开_直播解决方案,如何利用无人机进行直播

    近年来,无人机.运动相机等移动拍摄设备的涌现,让人们看到了不同的视角,随着无人机走向民众,无人机也被应用到了多个方面.网络直播的兴起,如何用无人机进行直播呢?美丽播能够提供无人机直播解决方案,面对无人 ...

  8. Android直播软件搭建左滑右滑清屏控件

    Android直播软件搭建左滑右滑清屏控件 最近在迭代直播软件搭建功能时,项目中之前的左滑清屏是用ViewPager实现的.这次迭代遇到一个布局层次导致的点击失效问题,继续用ViewPager的话改动 ...

  9. Android直播软件开发中接入腾讯IM大概流程是怎样的

    现阶段来看,直播软件中的即时通讯是非常重要的一个部分,毕竟直播过程中的交流和沟通是非常重要的,所以在Android直播软件开发时需要接入相关的IM服务. 通常我们选择的即时聊天服务,会选择集成简单方便 ...

最新文章

  1. WebService在开发中的实际问题
  2. 操作系统(二十七)管程
  3. [New Portal]Windows Azure Virtual Machine (18) Azure Virtual Machine内部IP和外部IP
  4. adsl 路由器默认密码
  5. Intel的新玩法:固态硬盘也超频
  6. Pandas系列(十一)Pandas中concat合并两个dataframe
  7. ad gerber文件生成_Gerber竟然可以倒转PCB文件!!!
  8. win7 操作mysql_win7系统如何设置Mysql密码保护数据库
  9. 使用AsyncDisplayKit提升UICollectionView和UITableView的滚动性能
  10. Windows 7 系统封装文字版 精简教程笔记!
  11. python实现逻辑回归算法
  12. matlab将三维bar图保存为emf格式时分辨率很低
  13. ios 渐变透明背景_PPT背景常见的6种设计方法
  14. 刘宇凡:新型鸡汤如何击溃传统鸡汤?
  15. 送一波福利,给「沉默王二」的读者朋友们
  16. 中国医疗器械行业需求态势及未来前景趋势预测报告(2022-2027年)
  17. 微信公众号关注回复,关键字回复全流程开发
  18. 众享比特 2018 LC3大会分享:如何基于Fabric实现供应链金融平台系统?
  19. 解决element的Table表格组件的高度问题( height只能是数字或者字符串 ),实现height: calc(100vh - 260px) 的效果
  20. 使用PL/Scope分析PL/SQL代码

热门文章

  1. 代码随想录训练营day45
  2. 计算机毕业设计Java汽车客运站票务管理系统(源码+系统+mysql数据库+lw文档)
  3. AI医疗 | 人脸识别、智能导诊系统上线,医号馆将引领基层医疗新时代
  4. python图片表格提取算法_python识别并提取表格中的文字--Apple的学习笔记
  5. abb机器人--示教器--基础认识
  6. LINUX下编译遇到“archiver requires 'AM_PROG_AR'”
  7. utf-8编码转中文
  8. 第二次上机实践项目-项目4-穷举法-换分币
  9. 为什么说大公司不是天堂,里面有哪些坑?
  10. 【架构】Lambda架构