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);
    }
}
相关推荐
大白要努力!几秒前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟1 小时前
Android音频采集
android·音视频
小白也想学C2 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程2 小时前
初级数据结构——树
android·java·数据结构
闲暇部落4 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX6 小时前
Android 分区相关介绍
android
大白要努力!7 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee7 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood8 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-11 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记