我们经常用直播,但是直播怎么做呢?前面的文章已经搭建好了推流服务器,现在就让我们先从采集数据开始吧,然后压缩数据,然后推流数据等等,一步步来,一定会成个大胖纸的。加油!
Activity.java
public class MainActivity extends Activity { static final String URL = "rtmp://112.74.96.116/live/jason"; private LivePusher live; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface); //相机图像的预览 live = new LivePusher(surfaceView.getHolder()); } /** * 开始直播 * @param btn */ public void mStartLive(View view) { Button btn = (Button)view; if(btn.getText().equals("开始直播")){ live.startPush(URL); btn.setText("停止直播"); }else{ live.stopPush(); btn.setText("开始直播"); } } /** * 切换摄像头 * @param btn */ public void mSwitchCamera(View btn) { live.switchCamera(); } }
定义接口Pusher.java
public abstract class Pusher { public abstract void startPush(); public abstract void stopPush(); public abstract void release(); }
视频Push
VideoPusher.java
public class VideoPusher extends Pusher implements Callback, PreviewCallback{ private SurfaceHolder surfaceHolder; private Camera mCamera; private VideoParam videoParams; private byte[] buffers; private boolean isPushing = false; private PushNative pushNative; public VideoPusher(SurfaceHolder surfaceHolder, VideoParam videoParams, PushNative pushNative) { this.surfaceHolder = surfaceHolder; this.videoParams = videoParams; this.pushNative = pushNative; surfaceHolder.addCallback(this); } @Override public void startPush() { isPushing = true; } @Override public void stopPush() { isPushing = false; } @Override public void surfaceCreated(SurfaceHolder holder) { startPreview(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void release() { stopPreview(); } /** * 切换摄像头 */ public void switchCamera() { if(videoParams.getCameraId() == CameraInfo.CAMERA_FACING_BACK){ videoParams.setCameraId(CameraInfo.CAMERA_FACING_FRONT); }else{ videoParams.setCameraId(CameraInfo.CAMERA_FACING_BACK); } //重新预览 stopPreview(); startPreview(); } /** * 开始预览 */ private void startPreview() { try { //SurfaceView初始化完成,开始相机预览 mCamera = Camera.open(videoParams.getCameraId()); mCamera.setPreviewDisplay(surfaceHolder); //获取预览图像数据 buffers = new byte[videoParams.getWidth() * videoParams.getHeight() * 4]; mCamera.addCallbackBuffer(buffers); mCamera.setPreviewCallbackWithBuffer(this); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } /** * 停止预览 */ private void stopPreview() { if(mCamera != null){ mCamera.stopPreview(); mCamera.release(); mCamera = null; } } @Override public void onPreviewFrame(byte[] data, Camera camera) { if(mCamera != null){ mCamera.addCallbackBuffer(buffers); } if(isPushing){ //回调函数中获取图像数据,然后给Native代码编码 pushNative.fireVideo(data); } } }
音频AudioPusher.java
public class AudioPusher extends Pusher{ private AudioParam audioParam; private AudioRecord audioRecord; private boolean isPushing = false; private int minBufferSize; private PushNative pushNative; public AudioPusher(AudioParam audioParam, PushNative pushNative) { this.audioParam = audioParam; this.pushNative = pushNative; int channelConfig = audioParam.getChannel() == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO; //最小缓冲区大小 minBufferSize = AudioRecord.getMinBufferSize(audioParam.getSampleRateInHz(), channelConfig, AudioFormat.ENCODING_PCM_16BIT); audioRecord = new AudioRecord(AudioSource.MIC, audioParam.getSampleRateInHz(), channelConfig, AudioFormat.ENCODING_PCM_16BIT, minBufferSize); } @Override public void startPush() { isPushing = true; //启动一个录音子线程 new Thread(new AudioRecordTask()).start(); } @Override public void stopPush() { isPushing = false; audioRecord.stop(); } @Override public void release() { if(audioRecord != null){ audioRecord.release(); audioRecord = null; } } class AudioRecordTask implements Runnable{ @Override public void run() { //开始录音 audioRecord.startRecording(); while(isPushing){ //通过AudioRecord不断读取音频数据 byte[] buffer = new byte[minBufferSize]; int len = audioRecord.read(buffer, 0, buffer.length); if(len > 0){ //传给Native代码,进行音频编码 pushNative.fireAudio(buffer, len); } } } } }
push总类,音频视频同时push
public class LivePusher implements Callback { private SurfaceHolder surfaceHolder; private VideoPusher videoPusher; private AudioPusher audioPusher; private PushNative pushNative; public LivePusher(SurfaceHolder surfaceHolder) { this.surfaceHolder = surfaceHolder; surfaceHolder.addCallback(this); prepare(); } /** * 预览准备 */ private void prepare() { pushNative = new PushNative(); //实例化视频推流器 VideoParam videoParam = new VideoParam(480, 320, CameraInfo.CAMERA_FACING_BACK); videoPusher = new VideoPusher(surfaceHolder,videoParam,pushNative); //实例化音频推流器 AudioParam audioParam = new AudioParam(); audioPusher = new AudioPusher(audioParam,pushNative); } /** * 切换摄像头 */ public void switchCamera() { videoPusher.switchCamera(); } /** * 开始推流 */ public void startPush(String url) { videoPusher.startPush(); audioPusher.startPush(); pushNative.startPush(url); } /** * 停止推流 */ public void stopPush() { videoPusher.stopPush(); audioPusher.stopPush(); pushNative.stopPush(); } /** * 释放资源 */ private void release() { videoPusher.release(); audioPusher.release(); pushNative.release(); } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { stopPush(); release(); } }
public class LivePusher implements Callback { private SurfaceHolder surfaceHolder; private VideoPusher videoPusher; private AudioPusher audioPusher; private PushNative pushNative; public LivePusher(SurfaceHolder surfaceHolder) { this.surfaceHolder = surfaceHolder; surfaceHolder.addCallback(this); prepare(); } /** * 预览准备 */ private void prepare() { pushNative = new PushNative(); //实例化视频推流器 VideoParam videoParam = new VideoParam(480, 320, CameraInfo.CAMERA_FACING_BACK); videoPusher = new VideoPusher(surfaceHolder,videoParam,pushNative); //实例化音频推流器 AudioParam audioParam = new AudioParam(); audioPusher = new AudioPusher(audioParam,pushNative); } /** * 切换摄像头 */ public void switchCamera() { videoPusher.switchCamera(); } /** * 开始推流 */ public void startPush(String url) { videoPusher.startPush(); audioPusher.startPush(); pushNative.startPush(url); } /** * 停止推流 */ public void stopPush() { videoPusher.stopPush(); audioPusher.stopPush(); pushNative.stopPush(); } /** * 释放资源 */ private void release() { videoPusher.release(); audioPusher.release(); pushNative.release(); } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { stopPush(); release(); } }
package com.dongnaoedu.live.params; /** * 视频数据参数 */ public class VideoParam { private int width; private int height; private int cameraId; public VideoParam(int width, int height, int cameraId) { super(); this.width = width; this.height = height; this.cameraId = cameraId; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getCameraId() { return cameraId; } public void setCameraId(int cameraId) { this.cameraId = cameraId; } }
当然数据有了,我们必须通过C++代码完成操作,所以如下:
/** * 调用C代码进行编码与推流 */ public class PushNative { public native void startPush(String url); public native void stopPush(); public native void release(); /** * 设置视频参数 * @param width * @param height * @param bitrate * @param fps */ public native void setVideoOptions(int width, int height, int bitrate, int fps); /** * 设置音频参数 * @param sampleRateInHz * @param channel */ public native void setAudioOptions(int sampleRateInHz, int channel); /** * 发送视频数据 * @param data */ public native void fireVideo(byte[] data); /** * 发送音频数据 * @param data * @param len */ public native void fireAudio(byte[] data, int len); static{ System.loadLibrary("dn_live"); } }
上面是第一节操作,我们后面的文章将慢慢介绍C++的实现,牵扯到jni的使用,可能会单独有一篇文章进行介绍,小朋友记得加关注哦。