android pcm播放器:有进度条同步、快进、快退、倍速功能

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);
    }
}
相关推荐
_Shirley1 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
hedalei3 小时前
RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题
android·android14·rk3576
锋风Fengfeng3 小时前
安卓多渠道apk配置不同签名
android
枫_feng3 小时前
AOSP开发环境配置
android·安卓
叶羽西4 小时前
Android Studio打开一个外部的Android app程序
android·ide·android studio
qq_171538855 小时前
利用Spring Cloud Gateway Predicate优化微服务路由策略
android·javascript·微服务
Vincent(朱志强)6 小时前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式
mmsx7 小时前
android 登录界面编写
android·登录界面
姜毛毛-JYM7 小时前
【JetPack】Navigation知识点总结
android
花生糖@8 小时前
Android XR 应用程序开发 | 从 Unity 6 开发准备到应用程序构建的步骤
android·unity·xr·android xr