安卓实现视频录制与显示和翻转摄像头

news2024/12/25 23:54:25

权限:

<!-- 相机权限 -->
<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />

<!-- 录音权限(包括麦克风权限) -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />

layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <TextureView
        android:id="@+id/textureView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/switchCameraButton"/>

    <Button
        android:id="@+id/switchCameraButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="翻转摄像头"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginBottom="16dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Activity:

package com.xmkjsoft.video_call;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.webrtc.MediaStream;

import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;


public class MainActivity extends AppCompatActivity {

    private MyWebSocketClient webSocketClient;
    private static final int PERMISSION_REQUEST_CAMERA = 1;
    private static final int PERMISSION_REQUEST_RECORD_AUDIO = 2;


    private static final int REQUEST_CAMERA_PERMISSION = 200;
    private TextureView textureView;
    private CameraDevice cameraDevice;
    private CaptureRequest.Builder captureRequestBuilder;
    private CameraCaptureSession cameraCaptureSession;

    private String cameraId;
    private MediaRecorder mediaRecorder;

    // 储存视频的文件路径
    private static final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath() + "/video.mp4";

    // 默认无参数构造函数
    public MainActivity() {
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        // 检查并请求相机权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CAMERA},
                    PERMISSION_REQUEST_CAMERA);
        }

        // 检查并请求录音权限(包括麦克风权限)
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.RECORD_AUDIO},
                    PERMISSION_REQUEST_RECORD_AUDIO);
        }



        // 设置按钮点击事件监听器
        Button switchCameraButton = findViewById(R.id.switchCameraButton);

        switchCameraButton.setOnClickListener(v -> {
            if (cameraDevice != null) {
                closeCamera();
                // 切换摄像头
                cameraId = (cameraId.equals("0")) ? "1" : "0"; // 更新 cameraId
                openCamera(); // 重新打开摄像头
            }
        });



        // 获取本地视频流
        textureView = findViewById(R.id.textureView);
        textureView.setSurfaceTextureListener(textureListener);

        // 初始化 MediaRecorder
        mediaRecorder = new MediaRecorder();
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mediaRecorder.setOutputFile(VIDEO_FILE_PATH);
        connectWebSocket();
    }





    private void connectWebSocket() {
        try {
            webSocketClient = new MyWebSocketClient("ws://192.168.28.218/ws/1233");
            webSocketClient.connect();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    // 内部类,用于处理 WebSocket 连接状态和消息
    private class MyWebSocketClient extends WebSocketClient {

        public MyWebSocketClient(String serverUri) throws URISyntaxException {
            super(new java.net.URI(serverUri));
        }

        @Override
        public void onOpen(ServerHandshake handshakedata) {
            // WebSocket 连接已打开
            System.out.println("WebSocket 连接已打开");
        }

        @Override
        public void onMessage(String message) {
            // 收到文本消息
            System.out.println("收到文本消息:" + message);
        }

        @Override
        public void onClose(int code, String reason, boolean remote) {
            // WebSocket 连接已关闭
            System.out.println("WebSocket 连接已关闭,code:" + code + ", reason:" + reason + ", remote:" + remote);
        }

        @Override
        public void onError(Exception ex) {
            // WebSocket 连接出错
            System.out.println("WebSocket 连接出错:" + ex.getMessage());
        }
    }





    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CAMERA) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 相机权限已授予
                System.out.println("相机权限已授予");
            } else {
                // 相机权限被拒绝
                System.out.println("相机权限被拒绝");
            }
        } else if (requestCode == PERMISSION_REQUEST_RECORD_AUDIO) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 录音权限已授予
                System.out.println("录音权限已授予");
            } else {
                // 录音权限被拒绝
                System.out.println("录音权限被拒绝");
            }
        }
    }






    private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
            openCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
        }

        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
        }
    };

    // 打开相机
    private void openCamera() {
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            // 检查 cameraId 是否为空
            if (cameraId == null) {
                // 如果为空,选择默认摄像头
                cameraId = manager.getCameraIdList()[0];
            }
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
                return;
            }
            manager.openCamera(cameraId, stateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            cameraDevice = camera;
            createCameraPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            cameraDevice.close();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int i) {
            cameraDevice.close();
            cameraDevice = null;
        }
    };




    private void createCameraPreview() {
        try {
            SurfaceTexture texture = textureView.getSurfaceTexture();
            texture.setDefaultBufferSize(1920, 1080);
            Surface surface = new Surface(texture);

            captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            captureRequestBuilder.addTarget(surface);

            cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                    if (cameraDevice == null) {
                        return;
                    }
                    MainActivity.this.cameraCaptureSession = cameraCaptureSession;
                    try {
                        // 开始预览
                        MainActivity.this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
                        // 添加 MediaRecorder 的 Surface
                        if (mediaRecorder != null) {
                            captureRequestBuilder.addTarget(mediaRecorder.getSurface());
                            // 开始录制视频
                            mediaRecorder.start();
                        }
                    } catch (CameraAccessException | IllegalStateException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                    Toast.makeText(MainActivity.this, "Failed", Toast.LENGTH_SHORT).show();
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    // 在 onStop() 方法中停止录制视频
    @Override
    protected void onStop() {
        super.onStop();
        stopRecording();
    }

    private void stopRecording() {
        if (mediaRecorder != null) {
            try {
                mediaRecorder.stop();
                mediaRecorder.reset();
                mediaRecorder.release();
            } catch (RuntimeException e) {
                e.printStackTrace();
            }
        }
    }





    // 关闭相机
    private void closeCamera() {
        if (cameraCaptureSession != null) {
            cameraCaptureSession.close();
            cameraCaptureSession = null;
        }
        if (cameraDevice != null) {
            cameraDevice.close();
            cameraDevice = null;
        }
    }
}

运行效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1663308.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

融资融券概念和操纵流程,案例解析

融资融券是一种金融工具&#xff0c;它允许投资者在证券市场上进行杠杆交易。简单来说&#xff0c;融资就是借钱买股票&#xff0c;融券就是借股票卖出。这种交易方式可以帮助投资者在短期内获得更高的收益&#xff0c;但同时也伴随着较高的风险。 案例背景&#xff1a; 假设…

云南区块链商户平台:抓包技术自制开票工具(二)

前言 上节我们分析了云南区块链商户平台的登录接口以及数据加密、解密&#xff0c;本节我们将构建一个项目框架&#xff0c;将大致的雏形制作出来 说明 由于我们使用开票软件都是在 云南区块链商户平台上操作&#xff0c;如果再开发电脑端就显得没必要&#xff0c;思考良久&…

百度公关一号位翻车的本质是,“精英主义”已经没有市场了 | 最新快讯

“精英主义”没市场了。 文&#xff5c;商隐社&#xff0c;作者 | 浩然 01 这几天商业圈持续发酵的热点新闻就是百度“公关一号位”璩静的“短视频翻车事件”。 一个名为“我是璩&#xff08;q&#xff09;静”&#xff0c;在自我介绍中标注了“百度副总裁”“公关一号位”“…

如何在Android手机上恢复已删除的视频?

有时&#xff0c;由于不同的原因&#xff0c;可能会发生意外的数据丢失灾难。 那么如何在Android手机内存或没有计算机的情况下恢复已删除的视频呢&#xff1f;本文将给你一个答案。 如何在Android上恢复已删除的视频&#xff1f; 不要惊慌&#xff01;您可以在Android手机上恢…

苹果M4芯片:推动AI时代的革新力量

随着科技的飞速发展&#xff0c;苹果公司一直以其创新精神引领着行业潮流。其中&#xff0c;M4芯片的推出无疑是苹果在人工智能领域迈出的重要一步。这款专为机器学习和AI计算而设计的芯片&#xff0c;不仅在新款iPad Pro等消费电子产品上亮相&#xff0c;更是预示着苹果即将开…

机器学习(四) ----------逻辑回归

目录 1 概述 2 极大似然估计 3 逻辑回归核心思想 3.1 对数似然损失&#xff08;Log-likelihood Loss&#xff09; 4 分类问题的评估方法 4.1 混淆矩阵&#xff08;Confusion Matrix&#xff09;&#xff1a; 4.2 准确率&#xff08;Accuracy&#xff09; 4.3 精确率&…

文本三剑客grep与正则表达式、元字符

正则表达式 正则表达式又称为正规表达式、常规表达式、在代码中常简写为regex、regex或RE。正则表达式是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串&#xff0c;简单来说&#xff0c;是一种匹配字符串的方法&#xff0c;通过一些特殊符号&#xff0c;实现快速查…

AcwingWeb应用课学习笔记

VSCode自动格式化 选中Format On Save不起作用 在设置中搜索default formatter&#xff0c;修改成Prettier-Code formatter meta标签 HTML 元素表示那些不能由其它 HTML 元相关&#xff08;meta-related&#xff09;元素&#xff08;(、,

全套停车场管理系统报价多少钱?停车场管理系统由哪些设备组成?

随着城市化进程的加快&#xff0c;汽车保有量的不断攀升&#xff0c;停车场的管理和运营成为城市基础设施建设的重要组成部分。一个高效、智能的停车场收费系统不仅能提升停车效率&#xff0c;还能增强用户体验&#xff0c;对城市的交通管理起到关键作用。本文将为您详细介绍全…

Linux NFS共享目录配置漏洞

Linux NFS共享目录配置漏洞 一、实验目的二、实验原理三、复现准备四、漏洞复现4.1、复现前提4.2、正式复现 一、实验目的 利用 NFS共享目录配置漏洞读取目标主机的 /etc/passwd 文件内容NFS 服务配置漏洞&#xff0c;赋予了根目录远程可写权限&#xff0c;导致 /root/.ssh/au…

TypeScript 中的类型与接口

在 TypeScript 中&#xff0c;定义类型有两种方式&#xff1a;“类型”和“接口”。 人们经常想知道该使用哪一种&#xff0c;答案并非适用于所有情况。有时一种更好&#xff0c;但在许多情况下&#xff0c;两者可以互换使用。 我们来详细了解一下类型和接口的不同点和相似点…

当时这样说就好了的笔记

系列文章目录 当时这样说就好了的笔记 文章目录 系列文章目录一、 不用好口才&#xff0c;怎么谈都讨喜的“说话金律”1、 掌握对方爱聊什么是交谈热络的第一步2、 装笨让对方当主角&#xff0c;和谁都能聊不停3、 “讲道理”谁都怕&#xff0c;坚持己见最伤感情4、 懂“聆听附…

Java——类与对象

目录 一、面向对象的初步认识 1.1 什么是面向对象 1.2 面向对象与面向过程 二、类的定义与使用 2.1 简单认识类 2.2 类的定义格式 三、类的实例化 3.1 什么是实例化 3.2 类和对象的说明 四、this引用 4.1 为什么要有this引用 4.2 什么是this引用 ​编辑 4.3 this引用…

数据增强,迁移学习,Resnet分类实战

目录 1. 数据增强&#xff08;Data Augmentation&#xff09; 2. 迁移学习 3. 模型保存 4. 102种类花分类实战 1. 数据集 2.导入包 3. 数据读取与预处理操作 4. Datasets制作输入数据 5.将标签的名字读出 6.展示原始数据 7.加载models中提供的模型 8.初始化…

轻松操作!ae导出mp4格式,一篇文章学会

在视频制作的过程中&#xff0c;Adobe After Effects作为一款强大而专业的后期处理工具&#xff0c;为我们提供了丰富的特效和编辑功能。然而&#xff0c;在完成创作后&#xff0c;将项目导出为通用的MP4格式是分享和展示作品的关键一步。在本文中&#xff0c;我们将探讨ae导出…

营销的本质是“利他”,资深运营高手分享9套消费返利玩转市场!

营销的本质是“利他”&#xff0c;资深运营高手分享9套消费返利玩转市场&#xff01; 文丨微三云营销总监胡佳东&#xff0c;点击上方“关注”&#xff0c;为你分享市场商业模式电商干货。 - 引言&#xff1a;2024年移动互联网基本已经占据了核心不可篡改的地位&#xff0c;而…

Graphormer:Transformer用于图预测任务

文章信息 文章题为“Do Transformers Really Perform Bad for Graph Representation?”&#xff0c;该文章发表于2021年NeurIPS会议上。文章提出Graphormer图预测任务。 摘要 Transformer架构已经成为许多领域的主导选择&#xff0c;例如自然语言处理和计算机视觉。此外…

1015: 堆排序算法

解法&#xff1a; 20240510_193050 最后一个非叶子节点就是最后一个节点的父节点 进行一次最小堆调整&#xff08;如视频&#xff09; #include<iostream> #include<vector> using namespace std; void min_heapfy(vector<int>& a,int sta,int end) {i…

游戏行业被攻击的原因、攻击种类及合适的服务器

很多游戏刚上线没多久就频繁遭到同行恶意攻击。在相关数据报告中&#xff0c;2023年上半年遭受DDoS攻击的行业中&#xff0c;游戏行业占到40%&#xff0c;而且攻击方式、攻击频率、攻击峰值呈明显上升趋势。很多充满创意的游戏开发公司刚才开发上线一个很有特色的产品&#xff…

在k8s中安装Grafana并对接Prometheus,实现k8s集群监控数据的展示

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Grafana&#xff1a;让数据说话的魔术师》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Grafana简介 2、Grafana的重要性与影响力 …