这个项目是手机的摄像头作为硬件,采集视频流信息,把视频流推流到rtmp服务器,然后在本地使用vlc打开推流的视频,如果可以收到视频流,则证明推流成功
直播
采集端:
音视频采集,视频处理(美颜,水印) 音视频编码压缩 把音视频封装FLV TS
常用框架
AVFoundation:数据
GPUImage:美颜
FFmpeg: 音频压缩
X264: 视频压缩
libremp: 推流
服务器流程:
数据分发CDN 鉴黄 截屏:展示主播画面 录制视频 实时转码
流媒体服务器
常用服务器 SNS BMS nginx
播放端流程
从FLV TS 分离中音视频数据 音视频解码 播放 聊天互动
播放端 观众
ijkplayer :播放
FFmpeg: 视频解码
VideoToolbox:视频硬解码
AudioToolbox:音频硬解码
github上有现成的开源实现,
推流、美颜、水印、弹幕、点赞动画、
滤镜、播放都有。技术其实不是很难,
而且现在很多云厂商都提供SDK,
七牛云、金山云、乐视云、腾讯云、百度云、
斗鱼直播伴侣推流端,功能几乎都是一样的,没啥亮点,
不同的是整个直播平台服务差异和接入的简易性。
后端现在 RTMP/HTTP-FLV 清一色,App挂个源站直接接入云厂商或CDN就OK。
1.先看效果图:
手机多种滤镜的运行效果:
因为代码实在太多,所以贴了一个项目地址,大概30M
项目地址:https://github.com/wrs13634194612/Rtmp-Video-Android.git
贴一下主要的核心代码:
1.清单文件申请权限:打开摄像头,麦克风采集声音信息,
2.主界面
package net.ossrs.yasea.demo;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.example.administrator.testz.R;
import com.github.faucamp.simplertmp.RtmpHandler;
import com.seu.magicfilter.utils.MagicFilterType;
import net.ossrs.yasea.SrsCameraView;
import net.ossrs.yasea.SrsEncodeHandler;
import net.ossrs.yasea.SrsPublisher;
import net.ossrs.yasea.SrsRecordHandler;
import java.io.IOException;
import java.net.SocketException;
import java.util.Random;
public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpListener,
SrsRecordHandler.SrsRecordListener, SrsEncodeHandler.SrsEncodeListener {
private static final String TAG = "Yasea";
private Button btnPublish;
private Button btnSwitchCamera;
private Button btnRecord;
private Button btnSwitchEncoder;
private Button btnPause;
private SrsCameraView mCameraView;
private SharedPreferences sp;
private String rtmpUrl = "rtmp://ossrs.net/" + getRandomAlphaString(3) + '/' + getRandomAlphaDigitString(5);
private String recPath = Environment.getExternalStorageDirectory().getPath() + "/test.mp4";
private SrsPublisher mPublisher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_main);
// response screen rotation event
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
// restore data.
sp = getSharedPreferences("Yasea", MODE_PRIVATE);
rtmpUrl = sp.getString("rtmpUrl", rtmpUrl);
// initialize url.
final EditText efu = (EditText) findViewById(R.id.url);
efu.setText(rtmpUrl);
btnPublish = (Button) findViewById(R.id.publish);
btnSwitchCamera = (Button) findViewById(R.id.swCam);
btnRecord = (Button) findViewById(R.id.record);
btnSwitchEncoder = (Button) findViewById(R.id.swEnc);
btnPause = (Button) findViewById(R.id.pause);
btnPause.setEnabled(false);
mCameraView = (SrsCameraView) findViewById(R.id.glsurfaceview_camera);
mPublisher = new SrsPublisher(mCameraView);
mPublisher.setEncodeHandler(new SrsEncodeHandler(this));
mPublisher.setRtmpHandler(new RtmpHandler(this));
mPublisher.setRecordHandler(new SrsRecordHandler(this));
mPublisher.setPreviewResolution(640, 360);
mPublisher.setOutputResolution(360, 640);
mPublisher.setVideoHDMode();
mPublisher.startCamera();
mCameraView.setCameraCallbacksHandler(new SrsCameraView.CameraCallbacksHandler(){
@Override
public void onCameraParameters(Camera.Parameters params) {
//params.setFocusMode("custom-focus");
//params.setWhiteBalance("custom-balance");
//etc...
}
});
btnPublish.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (btnPublish.getText().toString().contentEquals("publish")) {
rtmpUrl = efu.getText().toString();
SharedPreferences.Editor editor = sp.edit();
editor.putString("rtmpUrl", rtmpUrl);
editor.apply();
// mPublisher.startPublish(rtmpUrl);
mPublisher.startPublish("please input your rtmp address");
mPublisher.startCamera();
if (btnSwitchEncoder.getText().toString().contentEquals("soft encoder")) {
Toast.makeText(getApplicationContext(), "Use hard encoder", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "Use soft encoder", Toast.LENGTH_SHORT).show();
}
btnPublish.setText("stop");
btnSwitchEncoder.setEnabled(false);
btnPause.setEnabled(true);
} else if (btnPublish.getText().toString().contentEquals("stop")) {
mPublisher.stopPublish();
mPublisher.stopRecord();
btnPublish.setText("publish");
btnRecord.setText("record");
btnSwitchEncoder.setEnabled(true);
btnPause.setEnabled(false);
}
}
});
btnPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(btnPause.getText().toString().equals("Pause")){
mPublisher.pausePublish();
btnPause.setText("resume");
}else{
mPublisher.resumePublish();
btnPause.setText("Pause");
}
}
});
btnSwitchCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPublisher.switchCameraFace((mPublisher.getCameraId() + 1) % Camera.getNumberOfCameras());
}
});
btnRecord.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (btnRecord.getText().toString().contentEquals("record")) {
if (mPublisher.startRecord(recPath)) {
btnRecord.setText("pause");
}
} else if (btnRecord.getText().toString().contentEquals("pause")) {
mPublisher.pauseRecord();
btnRecord.setText("resume");
} else if (btnRecord.getText().toString().contentEquals("resume")) {
mPublisher.resumeRecord();
btnRecord.setText("pause");
}
}
});
btnSwitchEncoder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (btnSwitchEncoder.getText().toString().contentEquals("soft encoder")) {
mPublisher.switchToSoftEncoder();
btnSwitchEncoder.setText("hard encoder");
} else if (btnSwitchEncoder.getText().toString().contentEquals("hard encoder")) {
mPublisher.switchToHardEncoder();
btnSwitchEncoder.setText("soft encoder");
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
} else {
switch (id) {
case R.id.cool_filter:
mPublisher.switchCameraFilter(MagicFilterType.COOL);
break;
case R.id.beauty_filter:
mPublisher.switchCameraFilter(MagicFilterType.BEAUTY);
break;
case R.id.early_bird_filter:
mPublisher.switchCameraFilter(MagicFilterType.EARLYBIRD);
break;
case R.id.evergreen_filter:
mPublisher.switchCameraFilter(MagicFilterType.EVERGREEN);
break;
case R.id.n1977_filter:
mPublisher.switchCameraFilter(MagicFilterType.N1977);
break;
case R.id.nostalgia_filter:
mPublisher.switchCameraFilter(MagicFilterType.NOSTALGIA);
break;
case R.id.romance_filter:
mPublisher.switchCameraFilter(MagicFilterType.ROMANCE);
break;
case R.id.sunrise_filter:
mPublisher.switchCameraFilter(MagicFilterType.SUNRISE);
break;
case R.id.sunset_filter:
mPublisher.switchCameraFilter(MagicFilterType.SUNSET);
break;
case R.id.tender_filter:
mPublisher.switchCameraFilter(MagicFilterType.TENDER);
break;
case R.id.toast_filter:
mPublisher.switchCameraFilter(MagicFilterType.TOASTER2);
break;
case R.id.valencia_filter:
mPublisher.switchCameraFilter(MagicFilterType.VALENCIA);
break;
case R.id.walden_filter:
mPublisher.switchCameraFilter(MagicFilterType.WALDEN);
break;
case R.id.warm_filter:
mPublisher.switchCameraFilter(MagicFilterType.WARM);
break;
case R.id.original_filter:
default:
mPublisher.switchCameraFilter(MagicFilterType.NONE);
break;
}
}
setTitle(item.getTitle());
return super.onOptionsItemSelected(item);
}
@Override
protected void onStart() {
super.onStart();
if(mPublisher.getCamera() == null){
//if the camera was busy and available again
mPublisher.startCamera();
}
}
@Override
protected void onResume() {
super.onResume();
final Button btn = (Button) findViewById(R.id.publish);
btn.setEnabled(true);
mPublisher.resumeRecord();
}
@Override
protected void onPause() {
super.onPause();
mPublisher.pauseRecord();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPublisher.stopPublish();
mPublisher.stopRecord();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mPublisher.stopEncode();
mPublisher.stopRecord();
btnRecord.setText("record");
mPublisher.setScreenOrientation(newConfig.orientation);
if (btnPublish.getText().toString().contentEquals("stop")) {
mPublisher.startEncode();
}
mPublisher.startCamera();
}
private static String getRandomAlphaString(int length) {
String base = "abcdefghijklmnopqrstuvwxyz";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
private static String getRandomAlphaDigitString(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
private void handleException(Exception e) {
try {
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
mPublisher.stopPublish();
mPublisher.stopRecord();
btnPublish.setText("publish");
btnRecord.setText("record");
btnSwitchEncoder.setEnabled(true);
} catch (Exception e1) {
//
}
}
// Implementation of SrsRtmpListener.
@Override
public void onRtmpConnecting(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onRtmpConnected(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onRtmpVideoStreaming() {
}
@Override
public void onRtmpAudioStreaming() {
}
@Override
public void onRtmpStopped() {
Toast.makeText(getApplicationContext(), "Stopped", Toast.LENGTH_SHORT).show();
}
@Override
public void onRtmpDisconnected() {
Toast.makeText(getApplicationContext(), "Disconnected", Toast.LENGTH_SHORT).show();
}
@Override
public void onRtmpVideoFpsChanged(double fps) {
Log.i(TAG, String.format("Output Fps: %f", fps));
}
@Override
public void onRtmpVideoBitrateChanged(double bitrate) {
int rate = (int) bitrate;
if (rate / 1000 > 0) {
Log.i(TAG, String.format("Video bitrate: %f kbps", bitrate / 1000));
} else {
Log.i(TAG, String.format("Video bitrate: %d bps", rate));
}
}
@Override
public void onRtmpAudioBitrateChanged(double bitrate) {
int rate = (int) bitrate;
if (rate / 1000 > 0) {
Log.i(TAG, String.format("Audio bitrate: %f kbps", bitrate / 1000));
} else {
Log.i(TAG, String.format("Audio bitrate: %d bps", rate));
}
}
@Override
public void onRtmpSocketException(SocketException e) {
handleException(e);
}
@Override
public void onRtmpIOException(IOException e) {
handleException(e);
}
@Override
public void onRtmpIllegalArgumentException(IllegalArgumentException e) {
handleException(e);
}
@Override
public void onRtmpIllegalStateException(IllegalStateException e) {
handleException(e);
}
// Implementation of SrsRecordHandler.
@Override
public void onRecordPause() {
Toast.makeText(getApplicationContext(), "Record paused", Toast.LENGTH_SHORT).show();
}
@Override
public void onRecordResume() {
Toast.makeText(getApplicationContext(), "Record resumed", Toast.LENGTH_SHORT).show();
}
@Override
public void onRecordStarted(String msg) {
Toast.makeText(getApplicationContext(), "Recording file: " + msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onRecordFinished(String msg) {
Toast.makeText(getApplicationContext(), "MP4 file saved: " + msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onRecordIOException(IOException e) {
handleException(e);
}
@Override
public void onRecordIllegalArgumentException(IllegalArgumentException e) {
handleException(e);
}
// Implementation of SrsEncodeHandler.
@Override
public void onNetworkWeak() {
Toast.makeText(getApplicationContext(), "Network weak", Toast.LENGTH_SHORT).show();
}
@Override
public void onNetworkResume() {
Toast.makeText(getApplicationContext(), "Network resume", Toast.LENGTH_SHORT).show();
}
@Override
public void onEncodeIllegalArgumentException(IllegalArgumentException e) {
handleException(e);
}
}
布局:
说明:核心功能都在library里面,这个main只是调用一下方法,最后,启动,点击publish,开始推流,然后我们在vlc打开网络推流,就可以收到视频数据啦
vlc怎么使用,请看我上一篇博客,大疆无人机推流:https://blog.csdn.net/cf8833/article/details/92840672