我们经常用直播,但是直播怎么做呢?前面的文章已经搭建好了推流服务器,现在就让我们先从采集数据开始吧,然后压缩数据,然后推流数据等等,一步步来,一定会成个大胖纸的。加油!
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的使用,可能会单独有一篇文章进行介绍,小朋友记得加关注哦。