PCM(脉冲编码调制)是一种常见的数字音频编码格式,它代表原始音频数据的一种无损编码方式。以下是关于PCM格式的一些重要信息:
定义:PCM 是一种脉冲编码调制技术,它将模拟音频信号转换为数字形式,通过对模拟信号进行采样和量化,然后将样本表示为二进制编码来实现。PCM 不压缩音频数据,因此每个音频样本都以其原始值表示。
采样率:PCM 文件中的音频数据以一定的采样率(Samples Per Second,通常以Hz表示)进行采样。常见的采样率包括 44100 Hz(CD音质)、48000 Hz(DVD音质)和16000 Hz(电话音质)等。更高的采样率通常意味着更高的音频质量,但也会占用更多的存储空间。
位深度:PCM 数据以一定的位深度来表示每个样本的值,通常以位数表示。常见的位深度包括16位和24位。较高的位深度可以提供更好的音频质量,因为它可以更准确地表示音频振幅,但也会占用更多的存储空间。
声道数:PCM 可以是单声道(单声道)或立体声(双声道)等多声道格式。单声道表示音频仅具有一个声道,而立体声表示音频具有左右两个声道,允许立体声效果。
文件格式:PCM 数据通常存储在不同的文件格式中,如WAV(Waveform Audio File Format)或AIFF(Audio Interchange File Format)等。这些文件格式包含了PCM音频数据以及元数据,以描述音频的参数和格式。
总之,PCM 是一种直观的音频编码格式,它以原始的数字形式表示音频数据,没有压缩,因此在需要高音质的应用中很常见,如音乐制作和专业音频处理。但是,由于它不进行压缩,所以文件大小通常较大。
播放器工具类:
java
package com.realtop.translatemodule.utils;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import java.io.File;
import java.io.FileInputStream;
public class AudioTrackUtils {
private static final String TAG = "audio_track_utils";
private AudioTrack audioTrack;
private int sampleRate = 16000;
private int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private boolean isPlaying;
private Thread mThread;
private FileInputStream mFileInput;
private boolean isUseSpeed;
// 开启两倍速度
public void setUseSpeed(boolean useSpeed) {
isUseSpeed = useSpeed;
}
public final MutableLiveData<Float> PROGRESS_OBSERVER = new MutableLiveData<>();
public final MutableLiveData<Integer> PLAY_START_END = new MutableLiveData<>();
private final Handler mHandler = new Handler(Looper.getMainLooper());
public void playAudio(String filePath, int ratio) {
if (audioTrack != null) {
Log.i(TAG, "playAudio: is init");
return;
}
int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
Log.i(TAG, "playAudio: file path:" + filePath + "; buffer size:" + bufferSize);
audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
sampleRate,
channelConfig,
audioFormat,
bufferSize,
AudioTrack.MODE_STREAM
);
audioTrack.play();
isPlaying = true;
File sourceFile = new File(filePath);
try {
mFileInput = new FileInputStream(sourceFile);
long currentPos = (long) (ratio * 1.0f / 100 * sourceFile.length());
long skip = mFileInput.skip(currentPos);
Log.i(TAG, "playAudio: skip:" + skip);
} catch (Exception e) {
Log.i(TAG, "playAudio: error:" + e.getMessage());
}
mThread = new Thread(() -> {
byte[] bytes = new byte[bufferSize * 2];
byte[] real = new byte[bufferSize];// 倍速使用
int len = -1;
while (isPlaying) {
try {
len = mFileInput.read(bytes);
} catch (Exception e) {
Log.i(TAG, "playAudio: read file end:" + e.getMessage());
len = -1;// 默认异常退出
}
if (len == -1)
break;
if (isUseSpeed) {
for (int i = 0, j = 0; i < len * 0.25f; i += 4, j += 2) {
// damage
try {
real[j] = bytes[i];
real[j + 1] = bytes[i + 1];
} catch (Exception e) {
Log.i(TAG, "playAudio: speed change error:" + e.getMessage());
}
}
audioTrack.write(real, 0, (int) (len * 0.5f));
} else {
audioTrack.write(bytes, 0, len);
}
// 处理进度
handleProgress(sourceFile);
}
// 自动停止了
mHandler.post(this::stopAndRelease);
Log.i(TAG, "playAudio: looper end");
});
mThread.start();
PLAY_START_END.postValue(1);
Log.i(TAG, "playAudio: begin record");
}
private void handleProgress(File sourceFile) {
try {
long position = mFileInput.getChannel().position();
long length = sourceFile.length();
float progress = 1.0f * position / length;
if (progress < 0 || progress > 1) {
progress = 0;
}
PROGRESS_OBSERVER.postValue(progress * 100);
Log.i(TAG, "playAudio: pos:" + position + "; " + length);
} catch (Exception e) {
Log.i(TAG, "playAudio: error get pos:" + e.getMessage());
}
}
public void stopAndRelease() {
if (audioTrack == null) {
Log.i(TAG, "release: is ended");
return;
}
isPlaying = false;
try {
mThread.join();
mFileInput.close();
audioTrack.flush();
audioTrack.stop();
audioTrack.release();
Log.i(TAG, "release: end");
} catch (Exception e) {
Log.i(TAG, "release: error:" + e.getMessage());
}
audioTrack = null;
PLAY_START_END.postValue(2);
}
}