a:客户端:
一、 通过Camera2打开相机,代码如下
private void openCamera(Context context,String id) {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
// Add permission for camera and let user grant the permission
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED &&
checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
return;
}
manager.openCamera(id, stateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
cameraDevice = camera;
createCameraPreview();
}
@Override
public void onDisconnected(CameraDevice camera) {
camera.close();
cameraDevice = null;
}
@Override
public void onError(CameraDevice camera, int error) {
camera.close();
cameraDevice = null;
}
};
二、新建解码器MediaCodec以及队列,把录像数据暂时存放在队列中,等待解码器解码。MediaCodec采用异步的方式。因为camera2无法输出NV21数据格式,所以要通过算法把YUV数据转换成NV21格式,这样解码器才能正常解码
private void initImageReader(){
mImageReader = ImageReader.newInstance(video_width, video_height, ImageFormat.YUV_420_888, 2);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
//YUV420_888转NV21
....................
putQueueYUVData(new MyData(nv21, image.getTimestamp() / 1000, false));
image.close();
}
}, null /*mCameraHandler*/);
}
public static Queue<MyData> mQueue = new LinkedList<MyData>();
public void initMediaCodec(int width,int height){
this.width = width;
this.height = height;
//Preview Reader
MediaFormat format = createMediaFormat();
MediaCodecInfo info = selectCodec(MIME_TYPE);
try {
mEncoder = MediaCodec.createByCodecName(info.getName());
} catch (IOException e) {
e.printStackTrace();
}
mEncoder.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
//填充待解码NV21数据
....................
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo bufferInfo) {
ByteBuffer outputBuffer = mEncoder.getOutputBuffer(index);
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
if(bufferInfo.flags == BUFFER_FLAG_CODEC_CONFIG){
//发送sps pps
.............
}else if(bufferInfo.flags == BUFFER_FLAG_KEY_FRAME){
//发送I帧
.............
}else{
//发送非I帧
.............
}
mEncoder.releaseOutputBuffer(index, false);
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
}
});
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mEncoder.start();
}
三、利用Netty框架,同服务端建立连接,把H264数据发送带服务端
private void connect() {
try {
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap = new Bootstrap()
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(65535))
.channel(NioSocketChannel.class)
.group(group)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//写空闲30秒发送心跳
pipeline.addLast(new IdleStateHandler(30, HEART_TIME, 0));
//解码器
pipeline.addLast(
........
}
//编码器
).addLast(
...........
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(ip, port));
channelFuture.addListener(channelFutureListener);
channel = channelFuture.sync().channel();
Log.d(TAG, "实时监控服务器连接成功");
isConnect = true;
} catch (Exception e) {
e.printStackTrace();
isConnect = false;
Log.e(TAG, "实时监控服务器连接失败:" + e.getMessage());
}
}
/**
* 发送数据
*
* @param data
*/
public synchronized void sendData(byte[] data) {
if (channel != null) {
channel.writeAndFlush(data);
}
}
b: 服务端
一、监听端口,等待客户端连接
private void startServer(){
new Thread(new Runnable() {
@Override
public void run() {
int port = Configs.getInt("server.port", 10088);
//
try {
serverBootstrap = new ServerBootstrap();
serverBootstrap.option(ChannelOption.SO_BACKLOG, Configs.getInt("server.backlog", 102400));
bossGroup = new NioEventLoopGroup(Configs.getInt("server.worker-count", Runtime.getRuntime().availableProcessors()));
workerGroup = new NioEventLoopGroup();
businessGroup = new NioEventLoopGroup(4);
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(final SocketChannel channel) throws Exception {
.................
}
});
serverBootstrap.option(ChannelOption.SO_RCVBUF,1024*500);
serverBootstrap.option(ChannelOption.SO_SNDBUF,1024*500);
channel = serverBootstrap.bind(new InetSocketAddress(port)).sync().channel();
channel.closeFuture().sync();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}).start();
}
二、将接收到的数据,按照H264编码的特点(0001开头),组合成完整的一帧数据,给解码器解码。服务器解码采用同步的方法
private void startDecode(){
isRunning = true;
new Thread(new Runnable() {
@Override
public void run() {
while(isRunning){
if (NettyServerHandler.H264Queue.size() > 0) {
input = NettyServerHandler.H264Queue.poll();
} else {
input = null;
}
if(input!=null){
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(30);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10_00);
while (outputBufferIndex > 0) {
mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
}
}
}
}).start();
}
三、服务端可以通过接收客服端发送的NV21编码,生成图片,实现截图效果
private Bitmap nv21ToBitmap(byte[] nv21, int width, int height) {
Bitmap bitmap = null;
try {
YuvImage image = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, width, height), 80, stream);
bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
四、效果图
H264编解码
如果有疑问,联系QQ: 409259564