Android音频采集
在 Android 开发领域,音频采集是一项非常重要且有趣的功能。它为各种应用程序,如语音聊天、音频录制、多媒体内容创作等提供了基础支持。今天我们就来深入探讨一下 Android 音频采集的两大类型:Mic 音频采集和系统音频采集。
1. Mic音频采集
在 Android 中,我们通常使用 AudioRecord 类来实现 Mic 音频采集。
1.1 AudioRecord介绍
1.1.1 参数介绍
@RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes)
-
音频源(audioSource)
- 这个参数指定了音频数据的来源。例如,
MediaRecorder.AudioSource.MIC表示从设备的麦克风获取音频。除此之外,还有其他可选的音频源,如VOICE_RECOGNITION(用于语音识别)、VOICE_COMMUNICATION(用于语音通信,例如 VoIP 应用)等。不同的音频源在音频采集的特性上可能会有所不同,例如针对语音通信的音频源可能会对音频进行一些预处理,以优化语音传输的质量。
- 这个参数指定了音频数据的来源。例如,
-
采样率(sampleRateInHz)
- 采样率决定了每秒从音频信号中采集的样本数量。常见的采样率有 44100Hz(44.1kHz)和 16000Hz(16kHz)等。较高的采样率可以提供更高质量的音频,但同时也会产生更大的数据量。例如,44.1kHz 的采样率常用于音乐录制等对音质要求较高的场景,而 16kHz 的采样率在语音通信等场景中较为常见,因为它在保证一定语音清晰度的前提下,能够减少数据传输和处理的负担。
-
声道配置(channelConfig)
- 声道配置参数用于指定音频是单声道(
AudioFormat.CHANNEL_IN_MONO)还是立体声(AudioFormat.CHANNEL_IN_STEREO)。单声道音频只有一个音频通道,而立体声有两个通道,分别对应左右声道。在移动设备上,考虑到性能和存储空间等因素,单声道采集较为常用,并且可以在后期通过算法将单声道转换为立体声,以满足不同的应用需求。
- 声道配置参数用于指定音频是单声道(
-
音频格式(audioFormat)
- 音频格式参数确定了音频数据的编码格式。常见的有
AudioFormat.ENCODING_PCM_16BIT(16 位)和AudioFormat.ENCODING_PCM_8BIT(8 位)等。16 位格式能够提供更丰富的音频动态范围和更好的音质,但数据量相对较大。在 Android 手机等设备上,16 位 PCM 格式具有较好的兼容性,是比较常用的音频格式。
- 音频格式参数确定了音频数据的编码格式。常见的有
-
缓冲区大小(bufferSizeInBytes)
- 缓冲区大小是
AudioRecord中一个非常关键的参数。它决定了在音频采集过程中用于存储音频数据的缓冲区的大小。合适的缓冲区大小可以确保音频采集的流畅性,避免出现数据丢失或音频卡顿等问题。可以通过AudioRecord.getMinBufferSize方法来获取满足指定音频参数(采样率、声道配置和音频格式)的最小缓冲区大小。这个方法会根据设备的硬件性能和音频参数计算出一个合适的值,开发者通常可以根据这个最小值来合理设置缓冲区大小,例如可以适当增大缓冲区大小以应对一些复杂的音频处理场景,但过大的缓冲区可能会导致音频采集的延迟增加。
- 缓冲区大小是
1.1.2 工作流程
-
初始化
- 首先,需要使用合适的音频参数来创建
AudioRecord对象。通过调用AudioRecord的构造函数,传入音频源、采样率、声道配置、音频格式和缓冲区大小等参数来完成初始化。例如:
- 首先,需要使用合适的音频参数来创建
int bufferSize = AudioRecord.getMinBufferSize(mSampleRate, channelConfig, mAudioFormat);if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) {Log.e(TAG, "AudioRecord.getMinBufferSize failed: " + bufferSize);return;
}mBuffer = new byte[bufferSize];
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, mSampleRate, channelConfig, mAudioFormat, bufferSize);
- 如果
audioRecord.getState() == AudioRecord.STATE_INITIALIZED,则表示AudioRecord对象成功初始化,可以进行下一步操作。
-
开始采集
- 调用
audioRecord.startRecording()方法来启动音频采集。一旦开始采集,音频数据就会开始填充到缓冲区中。
- 调用
-
读取数据
- 通常会在一个单独的线程中读取缓冲区中的音频数据。可以使用
audioRecord.read()方法来读取数据。例如,将读取的数据存储到一个byte类型的数组中:
- 通常会在一个单独的线程中读取缓冲区中的音频数据。可以使用
int bytesRead = mAudioRecord.read(mBuffer, 0, mBuffer.length);
- 这个
readSize表示实际读取到的音频数据的大小。需要注意的是,在读取数据的过程中,要及时处理数据,避免缓冲区溢出。
-
停止采集和释放资源
- 当音频采集完成后,需要调用
audioRecord.stop()方法来停止采集,然后调用audioRecord.release()方法来释放AudioRecord对象占用的资源。这一步非常重要,因为如果不释放资源,可能会导致内存泄漏等问题。
- 当音频采集完成后,需要调用
1.2 具体实现
1.2.1 MainActivity
申请权限并打开CaptureActivity。
package com.skystack.mediaexporation;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;import com.skystack.mediaexporation.databinding.ActivityMainBinding;public class MainActivity extends AppCompatActivity {// Used to load the 'mediaexporation' library on application startup.static {System.loadLibrary("mediaexporation");}private ActivityMainBinding binding;private final static String TAG = MainActivity.class.getName();static private final String[] PERMISSION = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};private final static int RequestCodePermissions = 1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());if (!CheckPermission()) {ActivityCompat.requestPermissions(this, PERMISSION, RequestCodePermissions);}else{Init();}}private boolean CheckPermission(){boolean ret = true;for (String str : PERMISSION) {ret = ret && (ActivityCompat.checkSelfPermission(this, str) == PackageManager.PERMISSION_GRANTED);}return ret;}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == RequestCodePermissions) {if (!CheckPermission()) {Log.e(TAG, "request permissions denied");Toast.makeText(this, "request permissions denied", Toast.LENGTH_SHORT).show();finish();}else{Init();}}}private void Init(){}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(@NonNull MenuItem item) {switch (item.getItemId()){case R.id.capture:Toast.makeText(this, "capture", Toast.LENGTH_SHORT).show();CaptureActivity.IntentTo(this);break;case R.id.setting:Toast.makeText(this, "setting", Toast.LENGTH_SHORT).show();break;}return super.onOptionsItemSelected(item);}
}
1.2.2 CaptureActivity
采集控制及回调
package com.skystack.mediaexporation;import androidx.appcompat.app.AppCompatActivity;import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;import com.skystack.mediaexporation.databinding.ActivityCaptureBinding;import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class CaptureActivity extends AppCompatActivity implements AudioCapture.AudioCaptureCallback{private static final String TAG = CaptureActivity.class.getName();private ActivityCaptureBinding binding;private AudioCapture mAudioCapture;private File mAudioFile;private FileOutputStream mAudioOutputStream;public static Intent NewIntent(Context context){Intent intent = new Intent(context, CaptureActivity.class);return intent;}public static void IntentTo(Context context){context.startActivity(NewIntent(context));}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityCaptureBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());mAudioCapture = new AudioCapture(this, 44100, 2);binding.buttonStart.setOnClickListener(new View.OnClickListener() {boolean mIsCapture = false;@Overridepublic void onClick(View v) {mIsCapture = !mIsCapture;if(mIsCapture){if(mAudioFile == null) {mAudioFile = new File(Environment.getExternalStorageDirectory(), "audio.pcm");}if(mAudioOutputStream == null){try {mAudioOutputStream = new FileOutputStream(mAudioFile);} catch (FileNotFoundException e) {e.printStackTrace();}}mAudioCapture.StartRecord();binding.buttonStart.setText("停止采集");}else{mAudioCapture.StopRecording();binding.buttonStart.setText("采集音频");if(mAudioFile != null){try {mAudioOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}}});}@Overridepublic void OnAudioDataAvailable(byte[] data) {Log.i(TAG, "OnAudioDataAvailable: " + data.length);if(mAudioOutputStream != null){try {mAudioOutputStream.write(data);} catch (IOException e) {e.printStackTrace();}}}
}
1.2.3 AudioCapture
音频采集类
package com.skystack.mediaexporation;import android.annotation.SuppressLint;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;public class AudioCapture {private static final String TAG = AudioCapture.class.getName();private final int mSampleRate;private final int mChannels;private static int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;private AudioRecord mAudioRecord;private AudioCaptureCallback mCallback;private AudioRecordThread mRecordThread = null;private byte[] mBuffer = null;public AudioCapture(AudioCaptureCallback callback, int mSampleRate, int mChannels) {this.mCallback = callback;this.mSampleRate = mSampleRate;this.mChannels = mChannels;InitCapture();}private int ChannelCountToConfiguration(int channels) {return (channels == 1 ? android.media.AudioFormat.CHANNEL_IN_MONO : android.media.AudioFormat.CHANNEL_IN_STEREO);}@SuppressLint("MissingPermission")public void InitCapture() {int channelConfig = ChannelCountToConfiguration(mChannels);int bufferSize = AudioRecord.getMinBufferSize(mSampleRate, channelConfig, mAudioFormat);if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) {Log.e(TAG, "AudioRecord.getMinBufferSize failed: " + bufferSize);return;}mBuffer = new byte[bufferSize];mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, mSampleRate, channelConfig, mAudioFormat, bufferSize);if (mAudioRecord == null || mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {Log.e(TAG,"Failed to create a new AudioRecord instance");ReleaseAudioResources();return;}}public boolean StartRecord(){if(mAudioRecord == null) return false;if(mRecordThread != null) return false;try {mAudioRecord.startRecording();} catch (IllegalStateException e) {Log.e(TAG,"AudioRecord.startRecording failed: " + e.getMessage());return false;}if (mAudioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {Log.e(TAG, "AudioRecord.startRecording failed - incorrect state :"+ mAudioRecord.getRecordingState());return false;}mRecordThread = new AudioRecordThread();mRecordThread.start();return true;}public boolean StopRecording() {Log.d(TAG, "stopRecording");if(mRecordThread == null)return true;mRecordThread.StopThread();try {mRecordThread.join(2000);} catch (InterruptedException e) {e.printStackTrace();}mRecordThread = null;Log.d(TAG, "stopRecording done");return true;}public boolean DestroyRecording(){StopRecording();ReleaseAudioResources();return true;}private void ReleaseAudioResources() {Log.d(TAG, "releaseAudioResources");if (mAudioRecord != null) {mAudioRecord.release();mAudioRecord = null;}if(mBuffer != null){mBuffer = null;}}private class AudioRecordThread extends Thread {private volatile boolean mKeepAlive = true;@Overridepublic void run() {while (mKeepAlive){int bytesRead = mAudioRecord.read(mBuffer, 0, mBuffer.length);if(bytesRead == mBuffer.length){if(mCallback != null){mCallback.OnAudioDataAvailable(mBuffer);}} else {String errorMessage = "AudioRecord.read failed: " + bytesRead;Log.e(TAG, errorMessage);if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) {mKeepAlive = false;Log.e(TAG, errorMessage);}}}try {if(mAudioRecord != null){mAudioRecord.stop();}}catch (IllegalStateException e){Log.e(TAG, "AudioRecord.stop failed: " + e.getMessage());}}public void StopThread() {Log.d(TAG, "stopThread");mKeepAlive = false;}}public interface AudioCaptureCallback{void OnAudioDataAvailable(byte[] data);}}
2. 系统音频采集
系统音频采集有两种方法,但都有局限性。
2.1 REMOTE_SUBMIX
将AudioRecord的source设置为REMOTE_SUBMIX,REMOTE_SUBMIX会截断麦克风和耳机的声音,通过AudioRecord采集输出。
但是REMOTE_SUBMIX需要有system权限。适用于自己编的系统中采集音频,一般用在云手机等场景。
获取系统权限步骤:
- 在AndroidManifest.xml中声明系统权限,同时申请CAPTURE_AUDIO_OUTPUT权限。
android:sharedUserId="android.uid.system"
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
- 生成APK时,用与Android系统源码编译时一致的签名文件。
2.2 AudioPlaybackCapture
AudioPlaybackCapture API 是在 Android 10 中引入的。应用可以借助此 API 复制其他应用正在播放的音频。此功能类似于屏幕截图,但针对的是音频。主要用例是视频在线播放应用,这些应用希望捕获游戏正在播放的音频。
2.2.1 构建捕获应用
出于安全和隐私考虑,捕获播放的音频会施加一些限制。为了能够捕获音频,应用必须满足以下要求:
- 应用必须具有
RECORD_AUDIO权限。 - 应用必须调出
MediaProjectionManager.createScreenCaptureIntent()显示的提示,并且用户必须批准此提示。 - 捕获和播放音频的应用必须使用同一份用户个人资料。
如要从其他应用中捕获音频,您的应用必须构建AudioRecord 对象,并向其添AudioPlaybackCaptureConfiguration。请按以下步骤操作:
- 调用
AudioPlaybackCaptureConfiguration.Builder.build()以构建AudioPlaybackCaptureConfiguration - 通过调用
setAudioPlaybackCaptureConfig将配置传递给AudioRecord。
AudioFormat audioFormat = new AudioFormat.Builder().setChannelMask(channelConfig).setSampleRate(mSampleRate).setEncoding(mAudioFormat).build();AudioPlaybackCaptureConfiguration configuration =new AudioPlaybackCaptureConfiguration.Builder(mediaProjection).addMatchingUsage(AudioAttributes.USAGE_MEDIA).addMatchingUsage(AudioAttributes.USAGE_GAME).addMatchingUsage(AudioAttributes.USAGE_UNKNOWN).build();mAudioRecord = new AudioRecord.Builder().setAudioPlaybackCaptureConfig(configuration).setAudioFormat(audioFormat).setBufferSizeInBytes(bufferSize).build();
2.2.2 控制音频捕获
您的应用可以控制它可以录制的内容类型,以及哪些其他类型的应用可以录制自己的播放。
应用可以使用以下方法限制其可以捕获的音频:
- 将
AUDIO_USAGE传递给AudioPlaybackCaptureConfiguration.addMatchingUsage()可允许捕获特定用法。多次调用该方法可指定多个用法。 - 将
AUDIO_USAGE传递给AudioPlaybackCaptureConfiguration.excludeUsage()可禁止捕获相应用法。多次调用该方法可指定多个用法。 - 将 UID 传递到
AudioPlaybackCaptureConfiguration.addMatchingUid()可仅捕获具有特定 UID 的应用。多次调用该方法可指定多个 UID。 - 将 UID 传递到
AudioPlaybackCaptureConfiguration.excludeUid()可禁止捕获相应 UID。多次调用该方法可指定多个 UID。
请注意,您不能同时使用 addMatchingUsage() 和 excludeUsage() 方法。您必须选择其中之一。同样,您也不能同时使用 addMatchingUid() 和 excludeUid()。
2.2.3 获取MediaProjection
首先注册一个ActivityResultLauncher
mLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),new ActivityResultCallback<ActivityResult>() {@Overridepublic void onActivityResult(ActivityResult result) {if(result.getResultCode() == RESULT_OK){if(result.getData() != null){mAudioCaptureIntent = new Intent(captureActivity, AudioCaptureService.class);mAudioCaptureIntent.putExtra("resultCode", result.getResultCode());mAudioCaptureIntent.putExtra("data", result.getData());if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {startForegroundService(mAudioCaptureIntent);}Log.i(TAG, "获取屏幕录制权限成功");binding.buttonMedia.setText("停止采集");}}else{Log.e(TAG, "获取屏幕录制权限失败");binding.buttonMedia.setText("采集媒体");}}}
);
在开始采集时启动createScreenCaptureIntent
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
Intent screenCaptureIntent = mediaProjectionManager.createScreenCaptureIntent();
mLauncher.launch(screenCaptureIntent);
获取MediaProjection
注意:
- mediaProjection需要一个前台服务,所以获取MediaProjection需要在一个前台服务中运行,我们创建了一个前台service
AudioCaptureService。 - AndroidManifest.xml中的service中声明AudioCaptureService为类型为mediaProjection的前台服务。
<serviceandroid:name=".Capture.AudioCaptureService"android:foregroundServiceType="mediaProjection"android:enabled="true"android:exported="true"></service>
- 获取MediaProjection必须在startForeground之后。
3. Audacity播放
- 先从Andorid设备中导出保存的audio.pcm文件到Windows上。
- 打开Audacity。
- 文件->导入->原始数据,并选择audio.pcm文件。
- 按照采集时设置的格式,设置播放格式。
- 导入并播放。

4 完整代码
github
相关文章:
Android音频采集
在 Android 开发领域,音频采集是一项非常重要且有趣的功能。它为各种应用程序,如语音聊天、音频录制、多媒体内容创作等提供了基础支持。今天我们就来深入探讨一下 Android 音频采集的两大类型:Mic 音频采集和系统音频采集。 1. Mic音频采集…...
通过轻易云平台实现聚水潭数据高效集成到MySQL的技术方案
聚水潭数据集成到MySQL的技术案例分享 在本次技术案例中,我们将详细探讨如何通过轻易云数据集成平台,将聚水潭的数据高效、可靠地集成到MySQL数据库中。具体方案为“聚水谭-店铺查询单-->BI斯莱蒙-店铺表”。这一过程不仅需要处理大量数据的快速写入…...
类和对象( 中 【补充】)
目录 一 . 赋值运算符重载 1.1 运算符重载 1.2 赋值运算符重载 1.3 日期类实现 1.3.1 比较日期的大小 : 1.3.2 日期天数 : 1.3.3 日期 - 天数 : 1.3.4 前置/后置 1.3.5 日期 - 日期 1.3.6 流插入 << 和 流提取 >> 二 . 取地址运算符重载 2.1 const…...
如何使用gpio模拟mdio通信?
一、前言 实际项目开发中,由于设计原因,会将phy的mdio引脚连接到SoC的2个空闲gpio上, 这样就无法通过Gmac自有的架构实现修改phy, 因此只能通过GPIO模拟的方式实现MDIO, 好在Linux支持MDIO via GPIO功能。 该功能…...
C# 中的事件和委托:构建响应式应用程序
C#中的事件和委托。事件和委托是C#中用于实现观察者模式和异步回调的重要机制,它们在构建响应式和交互式应用程序中发挥着重要作用。以下是一篇关于C#中事件和委托的文章。 引言 事件和委托是C#语言中非常重要的特性,它们允许你实现观察者模式和异步回…...
科技赋能健康:多商户Java版商城系统引领亚健康服务数字化变革
在当今社会,随着生活节奏的加快和工作压力的增大,越来越多的人处于亚健康状态。据《The Lancet》期刊2023年的统计数据显示,全球亚健康状态的人群比例已高达82.8%,这一数字背后,隐藏着巨大的健康风险和社会成本。亚健康…...
区块链网络示意图;Aura共识和Grandpa共识(BFT共识)
目录 区块链网络示意图 Aura共识和Grandpa共识(BFT共识) Aura共识 Grandpa共识(BFT共识) Aura与Grandpa的结合 区块链网络示意图 CP Blockchain:这是中央处理区块链(或可能指某种特定的处理单元区块链)的缩写。它可能代表了该区块链网络的主要处理或存储单元。在这…...
Javaweb梳理18——JavaScript
今日目标 掌握 JavaScript 的基础语法掌握 JavaScript 的常用对象(Array、String)能根据需求灵活运用定时器及通过 js 代码进行页面跳转能通过DOM 对象对标签进行常规操作掌握常用的事件能独立完成表单校验案例 18.1 JavaScript简介 JavaScript 是一门跨…...
面向对象-接口的使用
1. 接口的概述 为什么有接口? 借口是一种规则,对于继承而言,部分子类之间有共同的方法,为了约束方法的使用,使用接口。 接口的应用: 接口不是一类事物,它是对行为的抽象。 2. 接口的定义和使…...
失落的Apache JDBM(Java Database Management)
简介 Apache JDBM(Java Database Management)是一个轻量级的、基于 Java 的嵌入式数据库管理系统。它主要用于在 Java 应用程序中存储和管理数据。这个项目已经过时了,只是发表一下以示纪念,现在已经大多数被SQLite和Derby代替。…...
Vue3+SpringBoot3+Sa-Token+Redis+mysql8通用权限系统
sa-token支持分布式token 前后端代码,地球号: bright12389...
MySQL 三大日志详解
在 MySQL 数据库中,binlog(二进制日志)、redo log(重做日志)和 undo log(回滚日志)起着至关重要的作用。它们共同保障了数据库的高可用性、数据一致性和事务的可靠性。下面将对这三大日志进行详…...
Java 岗面试八股文及答案整理(2024最新版)
春招,秋招,社招,我们 Java 程序员的面试之路,是挺难的,过了 HR,还得被技术面,小刀在去各个厂面试的时候,经常是通宵睡不着觉,头发都脱了一大把,还好最终侥幸能…...
Web3.0安全开发实践:Clarity最佳实践总结
在过去的一段时间里,CertiK团队对比特币生态系统及其发展进行了深入研究。同时,团队还审计了多个比特币项目以及基于不同编程语言的智能合约,包括OKX的BRC-20钱包和MVC DAO的sCrypt智能合约实现。 现在,我们的研究重点转向了Clar…...
基于Springboot+Vue动漫推荐平台管理系统(源码+lw+讲解部署+PPT)
前言 详细视频演示 论文参考 系统介绍 系统概述 核心功能 用户角色与功能 具体实现截图 1. 热门动漫功能 2. 文章专栏功能 3. 会员分享功能 4. 热门动漫管理功能(管理员端) 5. 动漫分类管理功能 技术栈 后端框架SpringBoot 前端框架Vue …...
秋意浓,森林披金装
秋意浓,森林披金装, 枫叶如火,漫山遍野狂。 松间轻风送寒意, 鸟鸣悠扬入云翔。 林间小径蜿蜒行, 落叶铺成金色毯。 溪水潺潺绕石转, 映出天边一抹霞。 野菊点缀在草间, 白云悠悠随意闲。…...
Chrome离线安装包下载
1、问Chrome的官网:https://www.google.cn/chrome/ 直接下载的是在线安装包,安装需要联网。 2、如果需要在无法联网的设备上安装Chrome,需要在上面的地址后面加上?standalone1。 Chrome离线安装包下载地址:https://www.google.c…...
安卓手机5G网络频繁掉4G 问题解决 手机5G网络优化方案
问题环境 在某个长期停留的位置(例如:躺平)使用手机时网络突然从5G跳到4G,偶尔跳来跳去导致网络体验很差,经过调整5G网络情况下网速及其他体验都要更好,基于这样的情况使用一种简单的操作,锁定5…...
使用LLaMA-Factory微调时的问题与解决方案记录
文章目录 如何指定微调使用的显卡如何解决显卡通信导致的报错模型微调的实际epoch和step如何计算如何实现多卡全量微调模型微调后的结果如何查看模型测试后的指标如何理解如何指定微调使用的显卡 启动网页时使用这种执行命令 CUDA_VISIBLE_DEVICES=5,6,7 llamafactory-cli we…...
Go语言switch语句
在Go语言中,switch,是一个高度灵活,其功能强大的控制结构,相比较Java中的switch,更受到语言重视。 目录 1.基础用法2.多值匹配3.不指定表达式的 switch4.使用 fallthrough 强制进入下一个分支5.使用类型断言的 switch…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
