一. MainActivity
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="呼叫"
android:onClick="connect"
/>
<SurfaceView
android:id="@+id/removeSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.maniu.videochata.LocalSurfaceView
android:id="@+id/localSurfaceView"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="10dp"
android:layout_width="100dp"
android:layout_height="100dp" />
</RelativeLayout>
public class MainActivity extends AppCompatActivity implements SocketLive.SocketCallback {
private SocketLive socketLive;
private SurfaceView removeSurfaceView;
private LocalSurfaceView localSurfaceView;
private DecodecPlayerLiveH265 decodecPlayerLiveH265;
private Surface surface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermission();
initView();
}
private void initView() {
removeSurfaceView = findViewById(R.id.removeSurfaceView);
localSurfaceView = findViewById(R.id.localSurfaceView);
removeSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
surface = holder.getSurface();
decodecPlayerLiveH265 = new DecodecPlayerLiveH265();
decodecPlayerLiveH265.initDecoder(surface);
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
});
}
public boolean checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
}, 1);
}
return false;
}
public void connect(View view) {
localSurfaceView.startCaptrue(this);
}
@Override
public void callBack(byte[] data) {
if (decodecPlayerLiveH265 != null) {
decodecPlayerLiveH265.callBack(data);
}
}
}
public class LocalSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {
private Camera.Size size;
private Camera mCamera;
EncodecPushLiveH265 encodecPushLiveH265;
public LocalSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (encodecPushLiveH265 != null) {
encodecPushLiveH265.encodeFrame(data);
}
}
@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) {
}
byte[] buffer;
private void startPreview() {
mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
Camera.Parameters parameters = mCamera.getParameters();
size = parameters.getPreviewSize();
try {
mCamera.setPreviewDisplay(getHolder());
mCamera.setDisplayOrientation(90);
buffer = new byte[size.width * size.height * 3 / 2];
mCamera.addCallbackBuffer(buffer);
mCamera.setPreviewCallbackWithBuffer(this);
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
public void startCaptrue(SocketLive.SocketCallback socketCallback){
encodecPushLiveH265 = new EncodecPushLiveH265(socketCallback,size.width, size.height);
encodecPushLiveH265.startLive();
}
}
二. EncodecPushLiveH265
public class EncodecPushLiveH265 {
private SocketLive socketLive;
private int width=1080;
private int height=1920;
private MediaCodec mediaCodec;
private byte[] nv12;
private byte[] yuv;
public static final int NAL_I = 19;
public static final int NAL_VPS = 32;
private byte[] vps_sps_pps_buf;
int frameIndex;
public EncodecPushLiveH265(SocketLive.SocketCallback socketCallback, int width, int height) {
this.socketLive = new SocketLive(socketCallback );
// 简历链接
socketLive.start();
this.width = width;
this.height = height;
}
public void startLive() {
try {
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC);
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, height, width);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1080 * 1920);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
int bufferLength = width*height*3/2;
nv12 = new byte[bufferLength];
yuv = new byte[bufferLength];
} catch (IOException e) {
e.printStackTrace();
}
}
public int encodeFrame(byte[] input) {
nv12=YuvUtils.nv21toNV12(input);
YuvUtils.portraitData2Raw(nv12, yuv, width, height);
int inputBufferIndex = mediaCodec.dequeueInputBuffer(100000);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
inputBuffer.clear();
inputBuffer.put(yuv);
long presentationTimeUs = computePresentationTime(frameIndex);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, yuv.length, 0, 0);
frameIndex ++;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex);
dealFrame(outputBuffer, bufferInfo);
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
return 0;
}
private long computePresentationTime(long frameIndex) {
return 240 + frameIndex * 1000000 / 15;
}
private void dealFrame(ByteBuffer bb, MediaCodec.BufferInfo bufferInfo) {
int offset = 4;
if (bb.get(2) == 0x01) {
offset = 3;
}
int type = (bb.get(offset) & 0x7E) >> 1;
if (type == NAL_VPS) {
vps_sps_pps_buf = new byte[bufferInfo.size];
bb.get(vps_sps_pps_buf);
} else if (type == NAL_I) {
final byte[] bytes = new byte[bufferInfo.size];
bb.get(bytes);
byte[] newBuf = new byte[vps_sps_pps_buf.length + bytes.length];
System.arraycopy(vps_sps_pps_buf, 0, newBuf, 0, vps_sps_pps_buf.length);
System.arraycopy(bytes, 0, newBuf, vps_sps_pps_buf.length, bytes.length);
this.socketLive.sendData(newBuf);
} else {
final byte[] bytes = new byte[bufferInfo.size];
bb.get(bytes);
this.socketLive.sendData(bytes);
}
}
}
三. DecodecPlayerLiveH265
public class DecodecPlayerLiveH265 {
private MediaCodec mediaCodec;
public void initDecoder(Surface surface) {
try {
mediaCodec = MediaCodec.createDecoderByType("video/hevc");
final MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, 1080, 1920);
format.setInteger(MediaFormat.KEY_BIT_RATE, 1080 * 1920);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mediaCodec.configure(format,
surface,
null, 0);
mediaCodec.start();
} catch (IOException e) {
e.printStackTrace();
}
}
public void callBack(byte[] data) {
int index= mediaCodec.dequeueInputBuffer(100000);
if (index >= 0) {
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);
inputBuffer.clear();
inputBuffer.put(data, 0, data.length);
mediaCodec.queueInputBuffer(index,
0, data.length, System.currentTimeMillis(), 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000);
while (outputBufferIndex >=0) {
mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
}
}
四. SocketLive
public class SocketLive {
private static final String TAG = "SocketLive";
private SocketCallback socketCallback;
private MyWebSocketClient myWebSocketClient;
public SocketLive(SocketCallback socketCallback ) {
this.socketCallback = socketCallback;
}
public void start() {
try {
URI url = new URI("ws://192.168.31.10:60002");
myWebSocketClient = new MyWebSocketClient(url);
myWebSocketClient.connect();
} catch (Exception e) {
e.printStackTrace();
}
}
public void sendData(byte[] bytes) {
if (myWebSocketClient!=null&&(myWebSocketClient.isOpen())) {
myWebSocketClient.send(bytes);
}
}
private class MyWebSocketClient extends WebSocketClient {
public MyWebSocketClient(URI serverURI) {
super(serverURI);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
Log.i(TAG, "打开 socket onOpen: ");
}
@Override
public void onMessage(String s) {
}
@Override
public void onMessage(ByteBuffer bytes) {
Log.i(TAG, "消息长度 : " + bytes.remaining());
byte[] buf = new byte[bytes.remaining()];
bytes.get(buf);
socketCallback.callBack(buf);
}
@Override
public void onClose(int i, String s, boolean b) {
Log.i(TAG, "onClose: ");
}
@Override
public void onError(Exception e) {
Log.i(TAG, "onError: ");
}
}
public interface SocketCallback {
void callBack(byte[] data);
}
}
网友评论