2021-06-11

直播
采集端:
音视频采集,视频处理(美颜,水印) 音视频编码压缩 把音视频封装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.清单文件申请权限:打开摄像头,麦克风采集声音信息,

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />

<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

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);
}

}
布局:

<?xml version="1.0" encoding="utf-8"?>

<net.ossrs.yasea.SrsCameraView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/glsurfaceview_camera"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_alignParentTop="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true" />
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Pause"
    android:id="@+id/pause"
    android:layout_above="@+id/publish"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true" />
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="publish"
    android:id="@+id/publish"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true" />

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="switch"
    android:id="@+id/swCam"
    android:layout_alignBottom="@+id/publish"
    android:layout_toRightOf="@+id/publish" />

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="record"
    android:id="@+id/record"
    android:layout_alignBottom="@+id/publish"
    android:layout_toRightOf="@id/swCam" />

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="soft encoder"
    android:id="@+id/swEnc"
    android:layout_alignBottom="@+id/publish"
    android:layout_toRightOf="@id/record"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"/>

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="14dp"
    android:id="@+id/url"
    android:textColor="@color/accent_material_light" />

说明:核心功能都在library里面,这个main只是调用一下方法,最后,启动,点击publish,开始推流,然后我们在vlc打开网络推流,就可以收到视频数据啦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值