Camera2预览转H264并通过Netty架构传输到服务端解码

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java通过Netty连接服务端和客户端的基本步骤如下: 1. 引入Netty依赖:在Maven中添加Netty依赖,或者手动下载并添加到项目中。 2. 创建客户端和服务端的Bootstrap:通过Bootstrap类创建客户端和服务端的启动类,设置相关的参数和处理器。 3. 客户端连接服务端:通过客户端的Bootstrap连接服务端的IP和端口。 4. 服务端监听客户端连接:通过服务端的Bootstrap监听客户端连接,建立连接后获取对应的Channel。 5. 处理服务端请求:在服务端的ChannelHandler中处理客户端的请求,包括连接数据库获取数据。 6. 发送数据到客户端:在服务端的ChannelHandler中将处理结果发送到客户端。 以下是一个简单的示例代码: 客户端: ``` EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel channel) throws Exception { // 添加处理器 } }); ChannelFuture future = bootstrap.connect("localhost", 8888).sync(); future.channel().closeFuture().sync(); ``` 服务端: ``` EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); Bootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel channel) throws Exception { // 添加处理器 } }); ChannelFuture future = bootstrap.bind(8888).sync(); future.channel().closeFuture().sync(); ``` 在处理器中连接数据库,获取数据,发送数据到客户端的代码如下: ``` public class ServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 连接数据库,获取数据 // 将数据发送到客户端 ctx.writeAndFlush(data); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值