1. Android オーディオ開発 (1): オーディオの基本
2. Android オーディオ開発 (2): オーディオの録音 (WAV および MP3 形式)
3. Android オーディオ開発 (3): ExoPlayer を使用したオーディオの再生
4. Android オーディオ開発 (4) :オーディオ再生モード
5.Androidオーディオ開発(5):誘導(画面オフ/明るい画面)管理
添付の GitHub ソース コード: MultimediaExplore
まず、オーディオの録音と再生の効果図を見てください。
CSDN はローカル ビデオのアップロードをサポートしていないため、最初にスクリーンショットをアップロードしました。
上記は録音です:長押しで録音、音波アニメーションのサポート、右スワイプで削除など。pcm、wav、mp3 形式のオーディオの録音をサポートします。
再生は次のとおりです。左側のスピーカー アイコンをクリックして、録音したばかりのローカル オーディオ ファイルの再生を開始し [オンライン オーディオ再生にも対応]、再生の進行状況をサポートし、再生モードの切り替え (ハンドセット/スピーカー/ヘッドフォン) などをサポートします。
1. 録音許可:
機能を開発する前に、最初に関連する権限を追加して申請する必要があります。これにより、フォローアップ作業が正常に進行します。音声録音に必要なアクセス許可は次のとおりです。これらの機密性の高いアクセス許可は、コードに動的に適用する必要があり、同意後にのみ通常どおり録音できます。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
次に、記録ファイルの構成:
最初のセクションで説明したオーディオの基本概念から、オーディオを録音する前に、録音に関連する構成を実行する必要があることがわかります。これにより、録音ファイルのオーディオ品質、ファイルサイズ、オーディオ形式などが直接決定されます。
/**
* 录音音频的相关配置
*/
private void initConfig() {
recordConfig = new RecordConfig();
//采样位宽
recordConfig.setEncodingConfig(AudioFormat.ENCODING_PCM_16BIT);
//录音格式
recordConfig.setFormat(RecordConfig.RecordFormat.MP3);
// recordConfig.setFormat(RecordConfig.RecordFormat.WAV);
//采样频率
recordConfig.setSampleRate(16000);
String recordDir = String.format(Locale.getDefault(), "%s/Record/zhongyao/",
Environment.getExternalStorageDirectory().getAbsolutePath());
//存储目录
recordConfig.setRecordDir(recordDir);
AudioRecordManager.getInstance().setCurrentConfig(recordConfig);
}
3. オーディオ録音:
オーディオ録音クラスには、主にAudioRecorderとAudioRecordManager の 2 つのカプセル化クラスがあります。
AudioRecorder:主にシステムのAudioRecord を録音に使用します。そして、録音したオーディオ ファイルをマージしたり、トランスコードしたりして、必要なオーディオ ファイルを生成します。このファイルはグローバルなシングルトンであり、オーディオ録音クラスのインスタンスが 1 つだけであることを保証します。
AudioRecordManager: AudioRecorder のカプセル化と管理、および外部世界との対話は、録音のためのさまざまなライフサイクル制御呼び出しを含め、すべてこのクラスを通じて行われます。外部の世界と AudioRecorder の間の直接的なやり取りが減り、より優れたレコーディング クラスの管理が実現されました.このクラスもグローバル シングルトン クラスです.
1. 記録オブジェクトを初期化します。
ここで、 bufferSizeInBytes [バッファ バイト サイズ]とaudioRecordオブジェクトは、主に以前の録音設定に基づいて生成されます。
/**
* 创建默认的录音对象
*/
public void prepareRecord() {
// 获得缓冲区字节大小
if (bufferSizeInBytes == 0) {
bufferSizeInBytes = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig());
}
if (audioRecord == null) {
audioRecord = new AudioRecord(AUDIO_INPUT, currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSizeInBytes);
}
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_PREPARE;
}
2. wav オーディオ ファイルを録音します。
wav音声ファイルはロスレスなので原音に近い音質になりますが、ロスレスだからこそwav音声ファイルはほぼ無圧縮で、比較的容量が大きくなります。
wav オーディオを録音するには、最初に録音して使用して pcm ファイルを取得し、次に pcm ファイルをマージし、最後にそれらを wav オーディオ ファイルに変換する必要があります。
(1) pcm ファイルの記録を開始します。
private void startPcmRecorder() {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_START;
notifyState();
Logger.d(TAG, "开始录制 Pcm");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(tmpFile);
audioRecord.startRecording();
byte[] byteBuffer = new byte[bufferSizeInBytes];
while (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_START) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
notifyData(byteBuffer);
fos.write(byteBuffer, 0, end);
fos.flush();
}
audioRecord.stop();
files.add(tmpFile);
if (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_STOP) {
makeFile();
} else {
Logger.d(TAG, "取消录制...");
}
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
notifyError("录音失败");
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (audioRecordStatus != AudioRecordStatus.AUDIO_RECORD_PAUSE) {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_IDLE;
notifyState();
Logger.d(TAG, "录音结束");
}
}
(2) 生成された複数の pcm ファイルをマージします。
/**
* 合并pcm文件
*/
private void mergePcmFile() {
boolean mergeSuccess = mergePcmFiles(resultFile, files);
if (!mergeSuccess) {
notifyError("合并失败");
}
}
(3) マージされた pcm ファイルを wav ファイルに変換します。
/**
* 添加Wav头文件
*/
private void makeWav() {
if (!FileUtil.isFile(resultFile) || resultFile.length() == 0) {
return;
}
byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());
WavUtils.writeHeader(resultFile, header);
}
3. MP3 オーディオ ファイルを録音する
WAV オーディオ ファイルと比較すると、MP3 オーディオ ファイルはより一般的であり、より商業的に使用されています。MP3 オーディオは圧縮されており、ファイル サイズは WAV の 12 分の 1 にすぎませんが、音質はほとんど優れていません。大きな違いはありません。ファイルの録音など、音質に高い要件がない場合は、MP3 形式が最適です。
(1) オーディオ バッファの録音を開始します。
ここでは Mp3EncodeThread というスレッドを開き、録音によって生成されたバイト配列 byteBuffer を連続的にエンコード、デコードして MP3 ファイルを生成します。
private void startMp3Recorder() {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_START;
notifyState();
try {
audioRecord.startRecording();
short[] byteBuffer = new short[bufferSizeInBytes];
while (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_START) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
if (mp3EncodeThread != null) {
mp3EncodeThread.addChangeBuffer(new Mp3EncodeThread.ChangeBuffer(byteBuffer, end));
}
notifyData(ByteUtils.toBytes(byteBuffer));
}
audioRecord.stop();
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
notifyError("录音失败");
}
if (audioRecordStatus != AudioRecordStatus.AUDIO_RECORD_PAUSE) {
if (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_CANCEL) {
deleteMp3Encoded();
} else {
stopMp3Encoded();
}
} else {
Logger.d(TAG, "暂停");
}
}
(2) MP3 オーディオコーデック:
Android のネイティブ オーディオ録音は、WAV ファイルの直接生成をサポートしていますが、実際には MP3 ファイルの直接生成をサポートしていません。これは、主にオープン ソースのlibmp3lame.so オーディオ コーデック ライブラリを使用する MP3 コーデックに対応します。以下は不完全なエンコードとデコードのメソッド、および Mp3Encoder クラスです。
MP3 コーデック方式:
private void lameData(ChangeBuffer changeBuffer) {
if (changeBuffer == null) {
return;
}
short[] buffer = changeBuffer.getData();
int readSize = changeBuffer.getReadSize();
if (readSize > 0) {
int encodedSize = Mp3Encoder.encode(buffer, buffer, readSize, mp3Buffer);
if (encodedSize < 0) {
Logger.e(TAG, "Lame encoded size: " + encodedSize);
}
try {
os.write(mp3Buffer, 0, encodedSize);
} catch (IOException e) {
Logger.e(e, TAG, "Unable to write to file");
}
}
}
Mp3Encoder クラス:
public class Mp3Encoder {
static {
System.loadLibrary("mp3lame");
}
public native static void close();
public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);
public native static int flush(byte[] mp3buf);
public native static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);
public static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate) {
init(inSampleRate, outChannel, outSampleRate, outBitrate, 7);
}
}
(3) libmp3lame.soを生成します。
このプロジェクトが提供するソース コードには lame の jni ソース コードが含まれています.独自のプロジェクトでlibmp3lame.soファイルを生成する場合は、 Mp3Encoder.c および Mp3Encoder.h ファイルを変更してから、so ファイルを生成する必要があります。 ndkビルドを介して。
変更内容は主に、ファイル内の Mp3Encoder パスを自分のプロジェクト内の Mp3Encoder パスに変更することです。そうしないと、関連するネイティブ メソッドが見つかりません。
また、CPUタイプはarmeabi-v7aとarm64-v8aが一般的ですが、他にも対応したい場合はApplication.mkに追加してください。
上記のファイルを変更した後、対応する libmp3lame.soファイルをndk-build コマンドでコンパイルおよび生成できます。
さまざまな CPU タイプのファイルを、次のように jniLibs の下、または sourceSets によって構成されたパスの下に配置します。
sourceSets.main {
jni.srcDirs = []//disable automatic ndk-build call
jniLibs.srcDirs = ['libs']
}
MP3オーディオ形式で録音できます。
4. オーディオ録音管理 [AudioRecordManager]:
グローバル シングルトン モードの AudioRecorderManager はビジネスとやり取りします。これにより、オーディオ録音インスタンスの単一性が保証されるだけでなく、オーディオ ライフサイクルの管理も向上します。
/**
* Create by zhongyao on 2021/8/18
* Description:音频录制管理类
*/
public class AudioRecordManager {
private static final String TAG = "AudioRecordManager";
private AudioRecordManager() {
}
public static AudioRecordManager getInstance() {
return AudioRecordManagerHolder.instance;
}
public static class AudioRecordManagerHolder {
public static AudioRecordManager instance = new AudioRecordManager();
}
public void setCurrentConfig(RecordConfig recordConfig) {
AudioRecorder.getInstance().setCurrentConfig(recordConfig);
}
public RecordConfig getCurrentConfig() {
return AudioRecorder.getInstance().getCurrentConfig();
}
/**
* 录音状态监听回调
*/
public void setRecordStateListener(RecordStateListener listener) {
AudioRecorder.getInstance().setRecordStateListener(listener);
}
/**
* 录音数据监听回调
*/
public void setRecordDataListener(RecordDataListener listener) {
AudioRecorder.getInstance().setRecordDataListener(listener);
}
/**
* 录音可视化数据回调,傅里叶转换后的频域数据
*/
public void setRecordFftDataListener(RecordFftDataListener recordFftDataListener) {
AudioRecorder.getInstance().setRecordFftDataListener(recordFftDataListener);
}
/**
* 录音文件转换结束回调
*/
public void setRecordResultListener(RecordResultListener listener) {
AudioRecorder.getInstance().setRecordResultListener(listener);
}
/**
* 录音音量监听回调
*/
public void setRecordSoundSizeListener(RecordSoundSizeListener listener) {
AudioRecorder.getInstance().setRecordSoundSizeListener(listener);
}
public void setStatus(AudioRecordStatus curAudioRecordStatus) {
switch (curAudioRecordStatus) {
case AUDIO_RECORD_IDLE:
break;
case AUDIO_RECORD_PREPARE:
AudioRecorder.getInstance().prepareRecord();
break;
case AUDIO_RECORD_START:
AudioRecorder.getInstance().startRecord();
break;
case AUDIO_RECORD_PAUSE:
AudioRecorder.getInstance().pauseRecord();
break;
case AUDIO_RECORD_STOP:
AudioRecorder.getInstance().stopRecord();
break;
case AUDIO_RECORD_CANCEL:
AudioRecorder.getInstance().cancelRecord();
break;
case AUDIO_RECORD_RELEASE:
AudioRecorder.getInstance().releaseRecord();
break;
default:
break;
}
}
public AudioRecordStatus getStatus() {
return AudioRecorder.getInstance().getAudioRecordStatus();
}
public String getAudioPath() {
return AudioRecorder.getInstance().getAudioPath();
}
}